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