
C'est une nouvelle sélection de trucs et astuces sur Python et la programmation de mon canal Telegram @pythonetc.
← 
Publications précédentesSi une instance d'une 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 
Il est assez simple pour une instance d'avoir un attribut qu'une classe n'a pas ou d'avoir l'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 ce n'est pas aussi simple, cependant, si vous voulez qu'une instance se comporte comme si elle n'avait pas d'attribut malgré que la classe l'ait. Pour y arriver, vous devez créer un descripteur personnalisé qui n'autorise pas l'accès à partir de l'instance:
 class ClassOnlyDescriptor: def __init__(self, value): self._value = value self._name = None  
Voir également comment fonctionne le décorateur de 
classonlymethod Django 
classonlymethod : 
https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6Les fonctions déclarées dans un corps de classe ne peuvent pas voir la portée de la classe. Cela a du sens puisque la portée de classe n'existe que lors de la création de 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 une 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 
De façon assez surprenante, il en va de même pour les compréhensions. Ils ont leurs propres étendues et ne peuvent pas accéder à l'étendue de classe également. Cela a vraiment du sens pour les compréhensions de générateur: ils évaluent les expressions une fois la création de classe terminée.
 >>> class A: ... x = 2 ... y = [x for _ in range(5)] ... [...] NameError: name 'x' is not defined 
Les compréhensions, cependant, n'ont pas accès à 
self . La seule façon de le faire fonctionner est d'ajouter une portée supplémentaire (oui, c'est moche):
 >>> class A: ... x = 2 ... y = (lambda x=x: [x for _ in range(5)])() ... >>> Ay [2, 2, 2, 2, 2] 
En Python, 
None est égal à 
None , il semble donc que vous pouvez vérifier 
None avec 
== :
 ES_TAILS = ('s', 'x', 'z', 'ch', 'sh') def make_plural(word, exceptions=None): if exceptions == None:  
C'est une mauvaise chose à faire cependant. 
None n'est en effet égal à 
None , mais ce n'est pas la seule chose qui existe. Les objets personnalisés peuvent également être égaux à 
None :
 >>> class A: ... def __eq__(self, other): ... return True ... >>> A() == None True >>> A() is None False 
La seule façon appropriée de comparer avec 
None est d'utiliser 
is None .
Les flotteurs Python peuvent avoir des valeurs NaN. Vous pouvez en obtenir un avec 
math.nan . 
nan n'est égal à rien, y compris lui-même:
 >>> math.nan == math.nan False 
De plus, l'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 que vous ne pouvez généralement 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 le type des générateurs. Vous pouvez également spécifier quel type est fourni, quel type peut être envoyé dans un générateur et ce qui est retourné. 
Generator[int, None, bool] est un générateur qui donne des entiers, retourne une valeur booléenne et ne prend pas en charge 
g.send() .
Voici un exemple un peu plus compliqué. 
chain_while cède à partir d'autres générateurs jusqu'à ce que l'un d'eux renvoie quelque chose qui est un signal d'arrêt selon 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, ))) 
Annoter une méthode d'usine n'est pas aussi simple qu'il y paraît. L'envie immédiate est d'utiliser quelque chose comme ceci:
 class A: @classmethod def create(cls) -> 'A': return cls() 
Cependant, ce n'est pas une bonne chose à faire. Le hic, c'est que 
create ne renvoie pas 
A , il retourne une instance de cls qui est 
A ou l' un de ses descendants. Regardez ce code:
 class A: @classmethod def create(cls) -> 'A': return cls() class B(A): @classmethod def create(cls) -> 'B': return super().create() 
Le résultat de la vérification mypy est une 
error: Incompatible return value type (got "A", expected "B") . Encore une fois, le problème est 
super().create() est annoté pour renvoyer 
A alors qu'il renvoie clairement 
B dans ce cas.
Vous pouvez résoudre ce problème en annotant cls avec 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() 
Maintenant, 
create retourne l'instance de la classe 
cls . Cependant, ces annotations sont trop lâches, nous avons perdu l'information que 
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() 
L'erreur est 
"Type[AType]" has no attribute "DATA" .
Pour résoudre ce problème, vous devez définir explicitement 
AType comme un sous-type de 
A avec l'argument 
bound de 
TypeVar :
 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()