Dicas e truques do meu canal de telegrama @pythonetc, agosto de 2019



É uma nova seleção de dicas e truques sobre Python e programação do meu canal Telegram @pythonetc.

Publicações anteriores


Se uma instância de uma classe não tiver um atributo com o nome fornecido, ela tentará acessar o atributo de classe com o mesmo nome.

>>> class A: ... x = 2 ... >>> Ax 2 >>> A().x 2 

É bastante simples para uma instância ter um atributo que uma classe não possui ou tem o atributo com o 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' 

Se não é tão simples, no entanto, se você deseja que uma instância se comporte como se ela não tivesse um atributo, apesar da classe. Para que isso aconteça, é necessário criar um descritor personalizado que não permita o acesso de acesso da instância:

 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 

Veja também como o decorador do Django classonlymethod funciona: https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6


As funções declaradas em um corpo de classe não podem ver o escopo da classe. Faz sentido, pois o escopo da classe existe apenas durante a criação da classe.

 >>> class A: ... x = 2 ... def f(): ... print(x) ... f() ... [...] NameError: name 'x' is not defined 

Isso geralmente não é um problema: os métodos são declarados dentro de uma classe apenas para se tornarem métodos e serem chamados posteriormente:

 >>> class A: ... x = 2 ... def f(self): ... print(self.x) ... >>> >>> >>> A().f() 2 

Surpreendentemente, o mesmo se aplica às compreensões. Eles têm seus próprios escopos e também não podem acessar o escopo da classe. Isso realmente faz sentido para as compreensões do gerador: elas avaliam expressões depois que a criação da classe já está concluída.

 >>> class A: ... x = 2 ... y = [x for _ in range(5)] ... [...] NameError: name 'x' is not defined 

As compreensões, no entanto, não têm acesso ao self . A única maneira de fazê-lo funcionar é adicionar mais um escopo (sim, isso é feio):

 >>> class A: ... x = 2 ... y = (lambda x=x: [x for _ in range(5)])() ... >>> Ay [2, 2, 2, 2, 2] 



No Python, None é igual a None então parece que você pode verificar None com == :

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

Isso é uma coisa errada a se fazer. None é de fato é igual a None , mas não é a única coisa que é. Objetos personalizados também podem ser iguais a None :

 >>> class A: ... def __eq__(self, other): ... return True ... >>> A() == None True >>> A() is None False 

A única maneira adequada de comparar com None é usar is None .


Os flutuadores Python podem ter valores de NaN. Você pode obter um com math.nan . nan não é igual a nada, inclusive a si próprio:

 >>> math.nan == math.nan False 

Além disso, o objeto NaN não é exclusivo, você pode ter vários objetos NaN diferentes de fontes diferentes:

 >>> float('nan') nan >>> float('nan') is float('nan') False 

Isso significa que você geralmente não pode usar o NaN como uma chave de dicionário:

 >>> d = {} >>> d[float('nan')] = 1 >>> d[float('nan')] = 2 >>> d {nan: 1, nan: 2} 


typing permite definir o tipo para geradores. Além disso, você pode especificar qual tipo é produzido, que tipo pode ser enviado para um gerador e o que é retornado. Generator[int, None, bool] é um gerador que gera números inteiros, retorna valor booleano e não suporta g.send() .

Aqui está um exemplo um pouco mais complicado. chain_while gera de outros geradores até que um deles retorne algo que é um sinal para parar de acordo com a função 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 um método de fábrica não é tão simples quanto parece. O desejo imediato é usar algo como isto:

 class A: @classmethod def create(cls) -> 'A': return cls() 

No entanto, isso não é uma coisa certa a se fazer. O problema é que, create não retorna A , ele retorna uma instância de cls que é A ou qualquer um de seus descendentes. Veja este código:

 class A: @classmethod def create(cls) -> 'A': return cls() class B(A): @classmethod def create(cls) -> 'B': return super().create() 

O resultado da verificação mypy é um error: Incompatible return value type (got "A", expected "B") . Novamente, o problema é super().create() é anotado para retornar A enquanto retorna claramente B nesse caso.

Você pode corrigir isso anotando cls com 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() 

Agora create retorna a instância da classe cls . No entanto, essas anotações são muito frouxas, perdemos a informação de que cls é um subtipo de A :

 AType = TypeVar('AType') class A: DATA = 42 @classmethod def create(cls: Type[AType]) -> AType: print(cls.DATA) return cls() 

O erro é "Type[AType]" has no attribute "DATA" .

Para corrigir isso, você precisa definir explicitamente AType como um subtipo de A com o 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/pt466317/


All Articles