É uma nova seleção de dicas e truques sobre Python e programação do meu canal Telegram @pythonetc.
←
Publicações anterioresVocê não pode alterar variáveis de fechamento simplesmente atribuindo-as. O Python trata a atribuição como uma definição dentro de um corpo de função e não faz nenhum fechamento.
Funciona bem, imprime
2
:
def make_closure(x): def closure(): print(x) return closure make_closure(2)
Lança
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 fazê-lo funcionar, você deve usar
nonlocal
. Diz explicitamente ao intérprete para não tratar 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 a iteração, você pode querer saber se é a primeira ou a última etapa do elemento da iteração. A maneira simples de lidar com isso é usar 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, ]
Você também pode processar o primeiro elemento fora do loop, que pode parecer mais claro, mas leva à duplicação de código até certo ponto. Também não é uma coisa simples de se fazer ao trabalhar com iterables abstratos:
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 verificar
i == 0
(funciona apenas para a detecção do primeiro elemento, não do último), mas a solução final pode ser um gerador que retorne o
first
e o
last
sinalizadores junto com o elemento de um iterável:
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
A função inicial agora pode ser 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ê deseja medir o tempo entre dois eventos, use
time.monotonic()
vez de
time.time()
.
time.monotonic()
nunca retrocede, mesmo que o relógio do sistema seja atualizado:
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 gerentes de contexto aninhados normalmente não sabem que estão aninhados. Você pode conhecê-los gerando gerentes de contexto interno pelo 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()

Se você deseja passar algumas informações ao longo da cadeia de chamadas, geralmente usa a maneira mais direta possível: você as transmite como argumentos de funções.
No entanto, em alguns casos, pode ser altamente inconveniente modificar todas as funções da cadeia para propagar alguns novos dados. Em vez disso, convém configurar algum tipo de contexto para ser usado por todas as funções da cadeia. Como esse contexto pode ser tecnicamente feito?
A solução mais simples é uma variável global. No Python, você também pode usar módulos e classes como detentores de contexto, uma vez que são, estritamente falando, variáveis globais também. Você provavelmente faz isso diariamente para coisas como madeireiros.
Se seu aplicativo for multiencadeado, uma variável global simples não funcionará para você, pois não é segura para encadeamento. Você pode ter mais de uma cadeia de chamadas em execução ao mesmo tempo e cada uma delas precisa de seu próprio contexto. O módulo
threading
fornece cobertura, ele fornece o objeto
threading.local()
que é seguro para threads. Armazene todos os dados acessando simplesmente os atributos:
threading.local().symbol = '@'
.
Ainda assim, as duas abordagens são inseguras para simultaneidade, o que significa que não funcionarão para a cadeia de chamadas de corotina, onde as funções não são apenas chamadas, mas também podem ser aguardadas. Uma vez que uma corotina
await
, um loop de eventos pode executar uma corotina completamente diferente de uma cadeia completamente 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 configurando o loop e restaurando o contexto sempre que alternar entre corotinas. Você pode fazer isso com o módulo
contextvars
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'), ))