Compilación @Pythonetc, agosto de 2019



Una nueva selección de consejos y programación de Python de mi feed @pythonetc.

Colecciones anteriores


Si 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 # 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 

Vea también cómo funciona el decorador classonlymethod Django: https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6


Las 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: # ← ← ← 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)) 

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

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


All Articles