@Pythonetc compilation, août 2019



Une nouvelle sélection de conseils et de programmation Python à partir de mon flux @pythonetc.

← Collections prĂ©cĂ©dentes


Si 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 # see __set_name__ def __get__(self, instance, owner): if instance is not None: raise AttributeError( f'{instance} has no attribute {self._name}' ) return self._value def __set_name__(self, owner, name): self._name = name class_only = ClassOnlyDescriptor class A: x = class_only(2) print(Ax) # 2 A().x # raises AttributeError 

Voir également comment fonctionne le décorateur Django de classonlymethod : https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6


Les 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: # ← ← ← exceptions = {} if word in exceptions: return exceptions[word] elif any(word.endswith(t) for t in ES_TAILS): return word + 'es' elif word.endswith('y'): return word[0:-1] + 'ies' else: return word + 's' exceptions = dict( mouse='mice', ) print(make_plural('python')) print(make_plural('bash')) print(make_plural('ruby')) print(make_plural('mouse', exceptions=exceptions)) 

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 B

Cela 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() 

Source: https://habr.com/ru/post/fr466315/


All Articles