
Une nouvelle sélection de conseils et de programmation Python à partir de mon flux @pythonetc.
â 
Collections prĂ©cĂ©dentesSi l'instance de classe n'a pas d'attribut avec le nom donnĂ©, elle essaie d'accĂ©der Ă  l'attribut de classe avec le mĂȘme nom.
>>> class A: ... x = 2 ... >>> Ax 2 >>> A().x 2 
Une instance peut facilement avoir un attribut que la classe n'a pas, ou avoir un attribut avec une valeur différente:
 >>> class A: ... x = 2 ... def __init__(self): ... self.x = 3 ... self.y = 4 ... >>> A().x 3 >>> Ax 2 >>> A().y 4 >>> Ay AttributeError: type object 'A' has no attribute 'y' 
Si vous voulez que l'instance se comporte comme si elle n'avait pas d'attribut, bien que la classe l'ait, vous devrez créer un descripteur personnalisé qui interdit l'accÚs à partir de cette instance:
 class ClassOnlyDescriptor: def __init__(self, value): self._value = value self._name = None  
Voir également comment fonctionne le décorateur Django de 
classonlymethod : 
https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6Les fonctions déclarées dans le corps de classe ne sont pas accessibles à la portée de cette classe. En effet, cette étendue existe uniquement lors de la création de la classe.
 >>> class A: ... x = 2 ... def f(): ... print(x) ... f() ... [...] NameError: name 'x' is not defined 
Ce n'est gĂ©nĂ©ralement pas un problĂšme: les mĂ©thodes sont dĂ©clarĂ©es dans la classe uniquement pour devenir des mĂ©thodes et ĂȘtre appelĂ©es plus tard:
 >>> class A: ... x = 2 ... def f(self): ... print(self.x) ... >>> >>> >>> A().f() 2 
Ătonnamment, il en va de mĂȘme pour les comprĂ©hensions. Ils ont leur propre portĂ©e et ils n'ont pas non plus accĂšs Ă  la portĂ©e des classes. C'est trĂšs logique en termes de comprĂ©hension des gĂ©nĂ©rateurs: le code qu'ils contiennent est exĂ©cutĂ© lorsque la classe est dĂ©jĂ  créée.
 >>> class A: ... x = 2 ... y = [x for _ in range(5)] ... [...] NameError: name 'x' is not defined 
Cependant, les compréhensions n'ont pas accÚs à 
self . La seule façon de donner accÚs à 
x est d'ajouter une autre portée (ouais, solution stupide):
 >>> class A: ... x = 2 ... y = (lambda x=x: [x for _ in range(5)])() ... >>> Ay [2, 2, 2, 2, 2] 
En Python, 
None équivalent à 
None , il peut donc sembler que vous pouvez vérifier 
None avec 
== :
 ES_TAILS = ('s', 'x', 'z', 'ch', 'sh') def make_plural(word, exceptions=None): if exceptions == None:  
Mais ce sera une erreur. Oui, 
None est égal à 
None , mais pas seulement. Les objets personnalisĂ©s peuvent Ă©galement ĂȘtre 
None :
 >>> class A: ... def __eq__(self, other): ... return True ... >>> A() == None True >>> A() is None False 
La seule façon correcte de comparer avec 
None est d'utiliser 
is None .
Les nombres Ă  virgule flottante en Python peuvent avoir des valeurs NaN. Par exemple, un tel nombre peut ĂȘtre obtenu en utilisant 
math.nan . 
nan Ă©gal Ă  rien, y compris lui-mĂȘme:
 >>> math.nan == math.nan False 
De plus, un objet NaN n'est pas unique; vous pouvez avoir plusieurs objets NaN différents provenant de différentes sources:
 >>> float('nan') nan >>> float('nan') is float('nan') False 
Cela signifie qu'en général, vous ne pouvez pas utiliser NaN comme clé de dictionnaire:
 >>> d = {} >>> d[float('nan')] = 1 >>> d[float('nan')] = 2 >>> d {nan: 1, nan: 2} 
typing vous permet de définir des types pour les générateurs. De plus, vous pouvez spécifier quel type est généré, qui est transmis au générateur et qui est renvoyé à l'aide de 
return . Par exemple, 
Generator[int, None, bool] génÚre des entiers, retourne des booléens et ne prend pas en charge 
g.send() .
Mais l'exemple est plus compliqué. 
chain_while donnĂ©es d'autres gĂ©nĂ©rateurs jusqu'Ă  ce que l'un d'eux renvoie une valeur qui est un signal d'arrĂȘt conformĂ©ment Ă  la fonction de 
condition :
 from typing import Generator, Callable, Iterable, TypeVar Y = TypeVar('Y') S = TypeVar('S') R = TypeVar('R') def chain_while( iterables: Iterable[Generator[Y, S, R]], condition: Callable[[R], bool], ) -> Generator[Y, S, None]: for it in iterables: result = yield from it if not condition(result): break def r(x: int) -> Generator[int, None, bool]: yield from range(x) return x % 2 == 1 print(list(chain_while( [ r(5), r(4), r(3), ], lambda x: x is True, ))) 
Définir des annotations pour la méthode d'usine n'est pas aussi simple qu'il y paraßt. Je veux juste utiliser quelque chose comme ça:
 class A: @classmethod def create(cls) -> 'A': return cls() 
Mais ce sera faux. L'astuce est que 
create ne renvoie pas 
A , il renvoie 
cls , qui est 
A ou l' un de ses descendants. Jetez un Ćil au code:
 class A: @classmethod def create(cls) -> 'A': return cls() class B(A): @classmethod def create(cls) -> 'B': return super().create() 
Le résultat du contrÎle mypy est l'erreur d' 
error: Incompatible return value type (got "A", expected "B") . Encore une fois, le problĂšme est que 
super().create() annoté comme retournant 
A , bien que dans ce cas, il retourne 
BCela peut ĂȘtre corrigĂ© si vous 
TypeVar cls utilisant 
TypeVar :
 AType = TypeVar('AType') BType = TypeVar('BType') class A: @classmethod def create(cls: Type[AType]) -> AType: return cls() class B(A): @classmethod def create(cls: Type[BType]) -> BType: return super().create() 
create retourne maintenant une instance de la classe 
cls . Cependant, ces annotations sont trop vagues, nous avons perdu des informations selon lesquelles 
cls est un sous-type de 
A :
 AType = TypeVar('AType') class A: DATA = 42 @classmethod def create(cls: Type[AType]) -> AType: print(cls.DATA) return cls() 
Nous 
"Type[AType]" has no attribute "DATA" erreur 
"Type[AType]" has no attribute "DATA" .
Pour y remédier, vous devez définir explicitement 
AType comme sous-type 
A Pour cela, 
TypeVar avec l'argument 
bound est utilisé.
 AType = TypeVar('AType', bound='A') BType = TypeVar('BType', bound='B') class A: DATA = 42 @classmethod def create(cls: Type[AType]) -> AType: print(cls.DATA) return cls() class B(A): @classmethod def create(cls: Type[BType]) -> BType: return super().create()