
Es una nueva selección de consejos y trucos sobre Python y la programación de mi canal de Telegram @pythonetc.
← 
Publicaciones anterioresSi una instancia de una clase no tiene un atributo con el nombre dado, intenta acceder al atributo de clase con el mismo nombre.
>>> class A: ... x = 2 ... >>> Ax 2 >>> A().x 2 
Es bastante simple para una instancia tener un atributo que una clase no tiene o tener el atributo con un valor diferente:
 >>> 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' 
Sin embargo, si no es tan simple, si desea que una instancia se comporte como si no tuviera un atributo a pesar de que la clase la tenga. Para que esto suceda, debe crear un descriptor personalizado que no permita el acceso de acceso desde la instancia:
 class ClassOnlyDescriptor: def __init__(self, value): self._value = value self._name = None  
Vea también cómo funciona el decorador de 
classonlymethod Django: 
https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6Las funciones declaradas en un cuerpo de clase no pueden ver el alcance de la clase. Tiene sentido ya que el alcance de la clase solo existe durante la creación de la clase.
 >>> class A: ... x = 2 ... def f(): ... print(x) ... f() ... [...] NameError: name 'x' is not defined 
Eso generalmente no es un problema: los métodos se declaran dentro de una clase solo para convertirse en métodos y se los llamará más tarde:
 >>> class A: ... x = 2 ... def f(self): ... print(self.x) ... >>> >>> >>> A().f() 2 
Sorprendentemente, lo mismo es cierto para las comprensiones. Tienen sus propios ámbitos y tampoco pueden acceder al ámbito de la clase. Eso realmente tiene sentido para las comprensiones del generador: evalúan las expresiones una vez que la creación de la clase ya ha finalizado.
 >>> class A: ... x = 2 ... y = [x for _ in range(5)] ... [...] NameError: name 'x' is not defined 
Las comprensiones, sin embargo, no tienen acceso a 
self . La única forma de hacerlo funcionar es agregar un alcance más (sí, eso es feo):
 >>> class A: ... x = 2 ... y = (lambda x=x: [x for _ in range(5)])() ... >>> Ay [2, 2, 2, 2, 2] 
En Python, 
None es igual a 
None por lo que parece que puede verificar 
None con 
== :
 ES_TAILS = ('s', 'x', 'z', 'ch', 'sh') def make_plural(word, exceptions=None): if exceptions == None:  
Sin embargo, esto es algo incorrecto. 
None es igual a 
None , pero no es lo único que es. Los objetos personalizados también pueden ser iguales a 
None :
 >>> class A: ... def __eq__(self, other): ... return True ... >>> A() == None True >>> A() is None False 
La única forma adecuada de comparar con 
None es usar 
is None .
Los flotadores de Python pueden tener valores NaN. Puedes conseguir uno con 
math.nan . 
nan no es igual a nada, incluido a sí mismo:
 >>> math.nan == math.nan False 
Además, el objeto NaN no es único, puede tener varios objetos NaN diferentes de diferentes fuentes:
 >>> float('nan') nan >>> float('nan') is float('nan') False 
Eso significa que generalmente no puede usar NaN como clave de diccionario:
 >>> d = {} >>> d[float('nan')] = 1 >>> d[float('nan')] = 2 >>> d {nan: 1, nan: 2} 
typing permite definir el tipo de generadores. También puede especificar qué tipo se produce, qué tipo se puede enviar a un generador y qué se devuelve. 
Generator[int, None, bool] es un generador que produce enteros, devuelve un valor booleano y no admite 
g.send() .
Aquí hay un ejemplo un poco más complicado. 
chain_while produce de otros generadores hasta que uno de ellos devuelve algo que es una señal para detenerse de acuerdo con la función 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, ))) 
Anotar un método de fábrica no es tan simple como parece. La urgencia inmediata es usar algo como esto:
 class A: @classmethod def create(cls) -> 'A': return cls() 
Sin embargo, eso no es lo correcto. El problema es que 
create no devuelve 
A , devuelve una instancia de cls que es 
A o cualquiera de sus descendientes. Mira este código:
 class A: @classmethod def create(cls) -> 'A': return cls() class B(A): @classmethod def create(cls) -> 'B': return super().create() 
El resultado de la comprobación mypy es un 
error: Incompatible return value type (got "A", expected "B") . Una vez más, el problema es 
super().create() se anota para devolver 
A mientras que claramente devuelve 
B en este caso.
Puede arreglar eso anotando cls con 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() 
Ahora 
create devuelve la instancia de la clase 
cls . Sin embargo, estas anotaciones son demasiado flojas, perdimos la información de que 
cls es un subtipo de 
A :
 AType = TypeVar('AType') class A: DATA = 42 @classmethod def create(cls: Type[AType]) -> AType: print(cls.DATA) return cls() 
El error es 
"Type[AType]" has no attribute "DATA" .
Para solucionarlo, debe definir explícitamente 
AType como un subtipo de 
A con el argumento 
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()