Es una nueva selección de consejos y trucos sobre Python y la programación de mi canal de Telegram @pythonetc.
←
Publicaciones anterioresNo puede mutar las variables de cierre simplemente asignándolas. Python trata la asignación como una definición dentro del cuerpo de una función y no cierra en absoluto.
Funciona bien, imprime
2
:
def make_closure(x): def closure(): print(x) return closure make_closure(2)
Lanza
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 que funcione, debe usar
nonlocal
. Le dice explícitamente al intérprete que no trate la asignación como una definición:
def make_closure(x): def closure(): nonlocal x print(x) x *= 2 print(x) return closure make_closure(2)()
A veces, durante la iteración, es posible que desee saber si es el primer o el último paso del elemento de la iteración. Una forma simple de manejar esto es usar un indicador 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, ]
También puede procesar el primer elemento fuera del bucle, que puede parecer más claro pero conduce a la duplicación de código en cierta medida. Tampoco es algo simple de hacer mientras se trabaja con iterables abstractos:
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
También puede usar
enumerate
y verificar si
i == 0
(funciona solo para la detección del primer elemento, no el último), pero la solución final podría ser un generador que devuelva las
first
y
last
banderas junto con el elemento de un 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
La función inicial ahora puede verse así:
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
Si desea medir el tiempo entre dos eventos, debe usar
time.monotonic()
lugar de
time.time()
.
time.monotonic()
nunca retrocede, incluso si se actualiza el reloj del 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()
Los gestores de contexto anidados normalmente no saben que están anidados. Puede hacer que se conozcan generando gestores de contexto interno por el 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()

Si desea pasar alguna información por la cadena de llamadas, generalmente usa la forma más directa posible: la pasa como argumentos de funciones.
Sin embargo, en algunos casos, puede ser muy inconveniente modificar todas las funciones de la cadena para propagar algunos datos nuevos. En cambio, es posible que desee configurar algún tipo de contexto para ser utilizado por todas las funciones en la cadena. ¿Cómo se puede hacer técnicamente este contexto?
La solución más simple es una variable global. En Python, también puede usar módulos y clases como titulares de contexto, ya que, estrictamente hablando, también son variables globales. Probablemente lo haga a diario para cosas como los madereros.
Si su aplicación es multiproceso, una variable global simple no funcionará para usted, ya que no es segura para subprocesos. Es posible que tenga más de una cadena de llamadas ejecutándose al mismo tiempo, y cada una de ellas necesita su propio contexto. El módulo de
threading
lo cubre, proporciona el objeto
threading.local()
que es seguro para subprocesos. Almacene allí cualquier dato simplemente accediendo a los atributos:
threading.local().symbol = '@'
.
Aún así, ambos enfoques son inseguros de concurrencia, lo que significa que no funcionarán para la cadena de llamadas de rutina donde las funciones no solo se llaman sino que también se pueden esperar. Una vez que una corutina
await
, un bucle de eventos puede ejecutar una corutina completamente diferente de una cadena completamente diferente. Eso no 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'), ))
Puede solucionarlo configurando el bucle y restaurando el contexto cada vez que cambia entre las rutinas. Puede hacerlo con el módulo
contextvars
desde 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'), ))