@Pythonetc compilation, julho de 2019


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

Coleções anteriores


Você 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'), )) 

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


All Articles