@Pythonetc setembro de 2018


Esta é a quarta coleção de dicas e programação em Python do meu feed @pythonetc .


Seleções anteriores:



Substituição e Sobrecarga


Existem dois conceitos fáceis de confundir: substituição e sobrecarga.


A substituição ocorre quando uma classe filho define um método já fornecido pelas classes pai e, portanto, o substitui. Em algumas linguagens, é necessário marcar explicitamente o método de substituição (em C # o modificador de override é usado) e, em algumas linguagens, isso é feito conforme desejado (anotação @Override em Java). O Python não requer o uso de um modificador especial e não fornece a marcação padrão desses métodos (para facilitar a leitura, alguém usa o decorador @override personalizado, que não faz nada).


Sobrecarga é outra história. Este termo refere-se a uma situação em que existem várias funções com o mesmo nome, mas com assinaturas diferentes. A sobrecarga é possível em Java e C ++; geralmente é usada para fornecer argumentos padrão:


 class Foo { public static void main(String[] args) { System.out.println(Hello()); } public static String Hello() { return Hello("world"); } public static String Hello(String name) { return "Hello, " + name; } } 

O Python não suporta a pesquisa de funções por assinatura, apenas por nome. Obviamente, você pode escrever um código que analise explicitamente os tipos e o número de argumentos, mas isso parecerá estranho, e é melhor evitar essa prática:


 def quadrilateral_area(*args): if len(args) == 4: quadrilateral = Quadrilateral(*args) elif len(args) == 1: quadrilateral = args[0] else: raise TypeError() return quadrilateral.area() 

Se você precisar de dicas de tipo, use o módulo de typing com o decorador @overload :


 from typing import overload @overload def quadrilateral_area( q: Quadrilateral ) -> float: ... @overload def quadrilateral_area( p1: Point, p2: Point, p3: Point, p4: Point ) -> float: ... 

Auto Vivificação


collections.defaultdict permite criar um dicionário que retorne um valor padrão se a chave solicitada estiver ausente (em vez de gerar um KeyError ). Para criar um defaultdict você precisa fornecer não apenas um valor padrão, mas uma fábrica desses valores.


Portanto, você pode criar um dicionário com um número praticamente infinito de dicionários aninhados, o que permite usar construções como d[a][b][c]...[z] .


 >>> def infinite_dict(): ... return defaultdict(infinite_dict) ... >>> d = infinite_dict() >>> d[1][2][3][4] = 10 >>> dict(d[1][2][3][5]) {} 

Esse comportamento é chamado de "auto-vivificação", um termo que vem do Perl.


Instanciação


A instanciação de objetos envolve duas etapas importantes. Primeiro, o método __new__ é chamado da classe, que cria e retorna um novo objeto. Em seguida, o Python chama o método __init__ , que define o estado inicial desse objeto.


No entanto, __init__ não será chamado se __new__ retornar um objeto que não é uma instância da classe original. Nesse caso, o objeto pode ser criado por outra classe, o que significa que __init__ já foi chamado no objeto:


 class Foo: def __new__(cls, x): return dict(x=x) def __init__(self, x): print(x) # Never called print(Foo(0)) 

Isso também significa que você não deve instanciar a mesma classe em __new__ usando o construtor regular ( Foo(...) ). Isso pode levar à execução repetida de __init__ ou até a recursão infinita.


Recursão infinita:


 class Foo: def __new__(cls, x): return Foo(-x) # Recursion 

Execução dupla __init__ :


 class Foo: def __new__(cls, x): if x < 0: return Foo(-x) return super().__new__(cls) def __init__(self, x): print(x) self._x = x 

A maneira correta:


 class Foo: def __new__(cls, x): if x < 0: return cls.__new__(cls, -x) return super().__new__(cls) def __init__(self, x): print(x) self._x = x 

O operador [] e fatias


No Python, você pode substituir o operador [] definindo o método mágico __getitem__ . Portanto, por exemplo, você pode criar um objeto que contenha praticamente um número infinito de elementos repetidos:


 class Cycle: def __init__(self, lst): self._lst = lst def __getitem__(self, index): return self._lst[ index % len(self._lst) ] print(Cycle(['a', 'b', 'c'])[100]) # 'b' 

O que é incomum aqui é que o operador [] suporta sintaxe exclusiva. Com ele, você pode obter não apenas [2] , mas também [2:10] , [2:10:2] , [2::2] e até [:] . A semântica do operador é: [start: stop: step], no entanto, você pode usá-lo de qualquer outra maneira para criar objetos personalizados.


Mas se você chamar __getitem__ com esta sintaxe, o que obterá como parâmetro de índice? É exatamente por isso que existem objetos de fatia.


 In : class Inspector: ...: def __getitem__(self, index): ...: print(index) ...: In : Inspector()[1] 1 In : Inspector()[1:2] slice(1, 2, None) In : Inspector()[1:2:3] slice(1, 2, 3) In : Inspector()[:] slice(None, None, None) 

Você pode até combinar a sintaxe de tuplas e fatias:


 In : Inspector()[:, 0, :] (slice(None, None, None), 0, slice(None, None, None)) 

slice não faz nada, apenas armazena os atributos start , stop e step .


 In : s = slice(1, 2, 3) In : s.start Out: 1 In : s.stop Out: 2 In : s.step Out: 3 

Interrompendo o Asyncio Coroutine


Qualquer assíncio executável de rotina asyncio pode ser abortado usando o método cancel() . Nesse caso, um CanceledError será enviado para a corotina, como resultado, esta e todas as corotinas associadas a ele serão interrompidas até que o erro seja capturado e suprimido.


CancelledError é uma subclasse de Exception , o que significa que pode ser capturada acidentalmente usando uma combinação de try ... except Exception , projetada para capturar "qualquer erro". Para detectar com segurança um bug de uma rotina, você deve fazer o seguinte:


 try: await action() except asyncio.CancelledError: raise except Exception: logging.exception('action failed') 

Planejamento de execução


Para planejar a execução de algum código em um determinado momento, o asyncio geralmente cria uma tarefa que executa await asyncio.sleep(x) :


 import asyncio async def do(n=0): print(n) await asyncio.sleep(1) loop.create_task(do(n + 1)) loop.create_task(do(n + 1)) loop = asyncio.get_event_loop() loop.create_task(do()) loop.run_forever() 

Mas criar uma nova tarefa pode ser caro, e não é necessário fazê-lo se você não planeja executar operações assíncronas (como a função do no meu exemplo). Em vez disso, você pode usar as funções loop.call_later e loop.call_at , que permitem agendar uma chamada de retorno de chamada assíncrona:


 import asyncio def do(n=0): print(n) loop = asyncio.get_event_loop() loop.call_later(1, do, n+1) loop.call_later(1, do, n+1) loop = asyncio.get_event_loop() do() loop.run_forever() 

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


All Articles