
Una nueva selección de consejos y programación de Python de mi feed @pythonetc.
← 
Colecciones anterioresSi la instancia de 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 
Una instancia puede tener fácilmente un atributo que la clase no tiene, o tener un 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' 
Si desea que la instancia se comporte como si no tuviera un atributo, aunque la clase lo tenga, deberá crear un descriptor personalizado que prohíba el acceso desde esta instancia:
 class ClassOnlyDescriptor: def __init__(self, value): self._value = value self._name = None  
Vea también cómo funciona el decorador 
classonlymethod Django: 
https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6Las funciones declaradas en el cuerpo de la clase no son accesibles para el alcance de esta clase. Esto se debe a que este alcance solo existe durante la creación de la clase.
 >>> class A: ... x = 2 ... def f(): ... print(x) ... f() ... [...] NameError: name 'x' is not defined 
Esto generalmente no es un problema: los métodos se declaran dentro de la 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 su propio alcance y tampoco tienen acceso al alcance de las clases. Esto es muy lógico en términos de comprensión del generador: el código en ellos se ejecuta cuando la clase ya está creada.
 >>> class A: ... x = 2 ... y = [x for _ in range(5)] ... [...] NameError: name 'x' is not defined 
Sin embargo, las comprensiones no tienen acceso a 
self . La única forma de proporcionar acceso a 
x es agregar otro ámbito (sí, solución estúpida):
 >>> class A: ... x = 2 ... y = (lambda x=x: [x for _ in range(5)])() ... >>> Ay [2, 2, 2, 2, 2] 
En Python, 
None equivalente a 
None , por lo que puede parecer que puede verificar 
None con 
== :
 ES_TAILS = ('s', 'x', 'z', 'ch', 'sh') def make_plural(word, exceptions=None): if exceptions == None:  
Pero eso será un error. Sí, 
None es igual a 
None , pero no solo eso. Los objetos personalizados también pueden ser 
None :
 >>> class A: ... def __eq__(self, other): ... return True ... >>> A() == None True >>> A() is None False 
La única forma correcta de comparar con 
None es usar 
is None .
Los números de coma flotante en Python pueden tener valores NaN. Por ejemplo, dicho número se puede obtener usando 
math.nan . 
nan no 
nan igual a nada, incluido a sí mismo:
 >>> math.nan == math.nan False 
Además, un objeto NaN no es único; puede tener varios objetos NaN diferentes de diferentes fuentes:
 >>> float('nan') nan >>> float('nan') is float('nan') False 
Esto significa que, en general, 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 tipos para generadores. Además, puede especificar qué tipo se genera, cuál se pasa al generador y cuál se devuelve con 
return . Por ejemplo, 
Generator[int, None, bool] genera enteros, devuelve booleanos y no admite 
g.send() .
Pero el ejemplo es más complicado. 
chain_while datos de otros generadores hasta que uno de ellos devuelve un valor que es una señal de parada 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, ))) 
Establecer anotaciones para el método de fábrica no es tan fácil como parece. Solo quiero usar algo como esto:
 class A: @classmethod def create(cls) -> 'A': return cls() 
Pero estará mal. El truco es que 
create devuelve no 
A , devuelve 
cls , que es 
A o uno de sus descendientes. Echa un vistazo al 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 el error de 
error: Incompatible return value type (got "A", expected "B") . Una vez más, el problema es que 
super().create() anota como que devuelve 
A , aunque en este caso devuelve 
BEsto se puede solucionar si anota 
cls usando 
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 ahora devuelve una instancia de la clase 
cls . Sin embargo, estas anotaciones son demasiado vagas, hemos perdido 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() 
Obtenemos 
"Type[AType]" has no attribute "DATA" error 
"Type[AType]" has no attribute "DATA" .
Para solucionarlo, debe definir explícitamente 
AType como un subtipo de 
A Para esto, se 
TypeVar con el argumento 
bound .
 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()