Esta é a terceira coleção de dicas e programação em Python do meu 
feed @pythonetc .
Seleções anteriores:
Método de fábrica
Se você criar novos objetos dentro de 
__init__ , será mais conveniente deixá-los prontos como argumentos e usar o método de fábrica para criar o objeto. Isso separará a lógica de negócios da implementação técnica da criação de objetos.
Neste exemplo, 
__init__ aceita 
host e 
port como argumentos para criar uma conexão com o banco de dados:
 class Query: def __init__(self, host, port): self._connection = Connection(host, port) 
Opção de refatoração:
 class Query: def __init__(self, connection): self._connection = connection @classmethod def create(cls, host, port): return cls(Connection(host, port)) 
Essa abordagem possui pelo menos as seguintes vantagens:
- A implantação é fácil. Nos testes, você pode fazer a Query(FakeConnection()).
- Uma classe pode ter quantos métodos de fábrica você desejar. Você pode criar uma conexão não apenas usando hosteport, mas também clonando outra conexão, lendo o arquivo de configuração, usando a conexão padrão etc.
- Métodos de fábrica similares podem ser transformados em funções assíncronas, o que é absolutamente impossível de fazer com __init__.
super ou próximo
A função 
super() permite que você faça referência à classe base. Isso pode ser muito útil nos casos em que a classe derivada deseja adicionar algo à implementação do método, em vez de substituí-lo completamente.
 class BaseTestCase(TestCase): def setUp(self): self._db = create_db() class UserTestCase(BaseTestCase): def setUp(self): super().setUp() self._user = create_user() 
O nome super não significa nada "super". Nesse contexto, significa "mais alto na hierarquia" (por exemplo, como na palavra "superintendente"). Ao mesmo tempo, 
super() nem sempre se refere à classe base; ele pode retornar facilmente uma classe filho. Portanto, seria mais correto usar o nome 
next() , pois a próxima classe é retornada de acordo com o MRO.
 class Top: def foo(self): return 'top' class Left(Top): def foo(self): return super().foo() class Right(Top): def foo(self): return 'right' class Bottom(Left, Right): pass  
Lembre-se de que 
super() pode produzir resultados diferentes, dependendo de onde o método foi originalmente chamado.
 >>> Bottom().foo() 'right' >>> Left().foo() 'top' 
Namespace personalizado para criar uma classe
Uma classe é criada em duas grandes etapas. Primeiro, o corpo da classe é executado, como o corpo de uma função. Na segunda etapa, o espaço para nome resultante (retornado por 
locals() ) é usado pela metaclasse (o padrão é o 
type ) para criar um objeto da classe.
 class Meta(type): def __new__(meta, name, bases, ns): print(ns) return super().__new__( meta, name, bases, ns ) class Foo(metaclass=Meta): B = 2 
Este código exibe 
{'__module__': '__main__', '__qualname__':'Foo', 'B': 3} .
Obviamente, se você introduzir algo como 
B = 2; B = 3 B = 2; B = 3 , a metaclasse verá apenas 
B = 3 , porque apenas esse valor está em 
ns . Essa limitação decorre do fato de que a metaclasse começa a funcionar somente após a execução do corpo.
No entanto, você pode intervir no procedimento de execução deslizando seu próprio espaço para nome. Um dicionário simples é usado por padrão, mas você pode fornecer seu próprio objeto, semelhante a um dicionário, se você usar o método 
__prepare__ da metaclasse.
 class CustomNamespace(dict): def __setitem__(self, key, value): print(f'{key} -> {value}') return super().__setitem__(key, value) class Meta(type): def __new__(meta, name, bases, ns): return super().__new__( meta, name, bases, ns ) @classmethod def __prepare__(metacls, cls, bases): return CustomNamespace() class Foo(metaclass=Meta): B = 2 B = 3 
Resultado da execução do código:
 __module__ -> __main__ __qualname__ -> Foo B -> 2 B -> 3 
Assim, o 
enum.Enum protegido contra 
duplicação .
matplotlib
matplotlib é uma biblioteca Python complexa e flexível para gráficos. Muitos produtos suportam, incluindo Jupyter e Pycharm. Aqui está um exemplo de renderização de um fractal simples usando 
matplotlib : 
https://repl.it/@VadimPushtaev/myplotlib (veja a imagem do título desta publicação).
Suporte a fuso horário
O Python fornece uma poderosa biblioteca de data e 
datetime para trabalhar com datas e horas. É interessante que os objetos de 
datetime tenham uma interface especial para suportar fusos horários (ou seja, o atributo 
tzinfo ), mas este módulo possui suporte limitado para a interface mencionada, portanto parte do trabalho é atribuída a outros módulos.
O mais popular deles é o 
pytz . Mas o fato é que o 
pytz não está totalmente em conformidade com a interface 
tzinfo . Isso é afirmado no início da documentação do 
pytz : "Esta biblioteca difere da API do Python documentada para implementações do tzinfo".
Você não pode usar objetos 
pytz como 
tzinfo . Se você tentar fazer isso, corre o risco de obter um resultado completamente insano:
 In : paris = pytz.timezone('Europe/Paris') In : str(datetime(2017, 1, 1, tzinfo=paris)) Out: '2017-01-01 00:00:00+00:09' 
Observe o deslocamento +00: 09. Pytz deve ser usado assim:
 In : str(paris.localize(datetime(2017, 1, 1))) Out: '2017-01-01 00:00:00+01:00' 
Além disso, após qualquer operação aritmética, é necessário aplicar a 
normalize aos objetos de 
datetime para evitar alterações de compensações (por exemplo, na fronteira do período do horário de verão).
 In : new_time = time + timedelta(days=2) In : str(new_time) Out: '2018-03-27 00:00:00+01:00' In : str(paris.normalize(new_time)) Out: '2018-03-27 01:00:00+02:00' 
Se você possui o Python 3.6, a documentação recomenda o uso de 
dateutil.tz vez de 
pytz . Esta biblioteca é totalmente compatível com o 
tzinfo , pode ser transmitida como um atributo e você não precisa usar a 
normalize . É verdade que funciona mais devagar.
Se você deseja saber por que o 
pytz não suporta a API 
datetime ou deseja ver mais exemplos, leia 
este artigo.
Magic StopIteration
Cada vez que o 
next(x) chamado, ele retorna um novo valor do iterador 
x até que uma exceção seja lançada. Se for 
StopIteration , o iterador estará esgotado e não poderá mais fornecer valores. Se o gerador for iterado, no final do corpo ele lançará automaticamente 
StopIteration :
 >>> def one_two(): ... yield 1 ... yield 2 ... >>> i = one_two() >>> next(i) 1 >>> next(i) 2 >>> next(i) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 
StopIteration pode ser manipulado automaticamente por ferramentas que chamam a 
next :
 >>> list(one_two()) [1, 2] 
Mas o problema é que qualquer StopIteration obviamente não esperada que surgir no corpo do gerador será silenciosamente aceita como um sinal do fim do gerador e não como um erro, como qualquer outra exceção:
 def one_two(): yield 1 yield 2 def one_two_repeat(n): for _ in range(n): i = one_two() yield next(i) yield next(i) yield next(i) print(list(one_two_repeat(3))) 
Aqui, o último 
yield é um erro: uma exceção 
StopIteration lançada interrompe a iteração da 
list(...) . Nós obtemos o resultado 
[1, 2] . No entanto, no Python 3.7 esse comportamento foi alterado. Alien 
StopIteration substituído por 
RuntimeError :
 Traceback (most recent call last): File "test.py", line 10, in one_two_repeat yield next(i) StopIteration The above exception was the direct cause of the following exception: Traceback (most recent call last): File "test.py", line 12, in <module> print(list(one_two_repeat(3))) RuntimeError: generator raised StopIteration 
Você pode usar 
__future__ import generator_stop para ativar o mesmo comportamento desde o Python 3.5.