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