Esta é a décima segunda coleção de dicas e programação em Python do meu feed @pythonetc.
←
Coleções anterioresVocê não pode alterar variáveis de fechamento com uma atribuição simples. O Python considera a atribuição como uma definição dentro de um corpo de função e não faz nenhum fechamento.
Funciona bem, exibe
2
:
def make_closure(x): def closure(): print(x) return closure make_closure(2)()
E esse código lança uma
UnboundLocalError: local variable 'x' referenced before assignment
:
def make_closure(x): def closure(): print(x) x *= 2 print(x) return closure make_closure(2)()
Para fazer o código funcionar, use
nonlocal
. Isso diz explicitamente ao intérprete para não considerar a atribuição como uma definição:
def make_closure(x): def closure(): nonlocal x print(x) x *= 2 print(x) return closure make_closure(2)()
Às vezes, durante uma iteração, você precisa descobrir qual elemento está sendo processado, primeiro ou último. Isso pode ser facilmente determinado usando um sinalizador explícito:
def sparse_list(iterable, num_of_zeros=1): result = [] zeros = [0 for _ in range(num_of_zeros)] first = True for x in iterable: if not first: result += zeros result.append(x) first = False return result assert sparse_list([1, 2, 3], 2) == [ 1, 0, 0, 2, 0, 0, 3, ]
Obviamente, você pode lidar com o primeiro elemento fora do loop. Isso parece mais limpo, mas leva à duplicação parcial do código. Além disso, não será tão fácil fazer isso ao trabalhar com
iterable
abstrato:
def sparse_list(iterable, num_of_zeros=1): result = [] zeros = [0 for _ in range(num_of_zeros)] iterator = iter(iterable) try: result.append(next(iterator)) except StopIteration: return [] for x in iterator: result += zeros result.append(x) return result
Você também pode usar
enumerate
e executar uma verificação
i == 0
(funciona apenas para determinar o primeiro elemento, não o último); no entanto, a melhor solução seria um gerador que retorne o
first
e o
last
sinalizadores junto com o elemento
iterable
:
def first_last_iter(iterable): iterator = iter(iterable) first = True last = False while not last: if first: try: current = next(iterator) except StopIteration: return else: current = next_one try: next_one = next(iterator) except StopIteration: last = True yield (first, last, current) first = False
Agora a função original pode ficar assim:
def sparse_list(iterable, num_of_zeros=1): result = [] zeros = [0 for _ in range(num_of_zeros)] for first, last, x in first_last_iter(iterable): if not first: result += zeros result.append(x) return result
Se você precisar medir o tempo decorrido entre dois eventos, use
time.monotonic()
vez de
time.time()
.
time.monotonic()
nunca muda na direção menor, mesmo ao atualizar o relógio do sistema:
from contextlib import contextmanager import time @contextmanager def timeit(): start = time.monotonic() yield print(time.monotonic() - start) def main(): with timeit(): time.sleep(2) main()
Os gerenciadores de contexto aninhados geralmente não sabem que estão aninhados. Você pode falar sobre isso criando gerentes internos usando um externo:
from contextlib import AbstractContextManager import time class TimeItContextManager(AbstractContextManager): def __init__(self, name, parent=None): super().__init__() self._name = name self._parent = parent self._start = None self._substracted = 0 def __enter__(self): self._start = time.monotonic() return self def __exit__(self, exc_type, exc_value, traceback): delta = time.monotonic() - self._start if self._parent is not None: self._parent.substract(delta) print(self._name, 'total', delta) print(self._name, 'outer', delta - self._substracted) return False def child(self, name): return type(self)(name, parent=self) def substract(self, n): self._substracted += n timeit = TimeItContextManager def main(): with timeit('large') as large_t: with large_t.child('medium') as medium_t: with medium_t.child('small-1'): time.sleep(1) with medium_t.child('small-2'): time.sleep(1) time.sleep(1) time.sleep(1) main()

Quando você precisa passar informações em uma cadeia de chamadas, a primeira coisa que vem à mente é passar dados na forma de argumentos de função.
Em alguns casos, pode ser muito mais conveniente modificar todas as funções da cadeia para transferir uma nova parte de dados. Em vez disso, você pode especificar um contexto que será usado por todas as funções da cadeia. Como fazer isso?
A solução mais fácil é usar uma variável global. No Python, você também pode usar módulos e classes como mantenedores de contexto, porque, estritamente falando, elas também são variáveis globais. Você provavelmente já faz isso regularmente, por exemplo, para registro no diário.
Se seu aplicativo for multithread, as variáveis globais comuns não funcionarão para você, uma vez que elas não são seguras para threads. A cada momento, você pode ter várias cadeias de chamadas e cada uma delas precisa de seu próprio contexto. O módulo
threading
o ajudará, ele fornece um objeto
threading.local()
, que é seguro para threads. Você pode armazenar dados nele com um acesso simples aos atributos:
threading.local().symbol = '@'
.
No entanto, as duas abordagens descritas não são compatíveis com simultaneidade, ou seja, não são adequadas para a cadeia de chamadas da Coroutine, na qual o sistema não apenas chama funções, mas também espera que sejam executadas. Quando uma corotina é executada em
await
, um fluxo de eventos pode acionar outra corotina de uma cadeia diferente. Isso não vai funcionar:
import asyncio import sys global_symbol = '.' async def indication(timeout): while True: print(global_symbol, end='') sys.stdout.flush() await asyncio.sleep(timeout) async def sleep(t, indication_t, symbol='.'): loop = asyncio.get_event_loop() global global_symbol global_symbol = symbol task = loop.create_task( indication(indication_t) ) await asyncio.sleep(t) task.cancel() loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather( sleep(1, 0.1, '0'), sleep(1, 0.1, 'a'), sleep(1, 0.1, 'b'), sleep(1, 0.1, 'c'), ))
Você pode corrigir isso forçando o ciclo a definir e restaurar o contexto sempre que alternar entre corotinas. Você pode implementar esse comportamento usando o módulo
contextvars
, que está disponível desde o Python 3.7.
import asyncio import sys import contextvars global_symbol = contextvars.ContextVar('symbol') async def indication(timeout): while True: print(global_symbol.get(), end='') sys.stdout.flush() await asyncio.sleep(timeout) async def sleep(t, indication_t, symbol='.'): loop = asyncio.get_event_loop() global_symbol.set(symbol) task = loop.create_task(indication(indication_t)) await asyncio.sleep(t) task.cancel() loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather( sleep(1, 0.1, '0'), sleep(1, 0.1, 'a'), sleep(1, 0.1, 'b'), sleep(1, 0.1, 'c'), ))