Consejos y trucos de mi canal de Telegram @pythonetc, agosto de 2019



Es una nueva selección de consejos y trucos sobre Python y la programación de mi canal de Telegram @pythonetc.

Publicaciones anteriores


Si 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 # 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 de classonlymethod Django: https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6


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

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

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


All Articles