@Pythonetc Aug de 2018



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 host e port , 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 # prints 'right' print(Bottom().foo()) 

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.

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


All Articles