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