Esta es la duodécima colección de consejos y programación de Python de mi feed @pythonetc.
←
Colecciones anterioresNo puede cambiar las variables de cierre con una simple asignación. Python considera la asignación como una definición dentro del cuerpo de una función y no hace ningún cierre en absoluto.
Funciona bien, muestra
2
:
def make_closure(x): def closure(): print(x) return closure make_closure(2)()
Y este código arroja un
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 el código funcione, use
nonlocal
. Esto le dice explícitamente al intérprete que no considere 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 una iteración, necesita averiguar qué elemento se está procesando, primero o último. Esto se puede determinar fácilmente usando una bandera explícita:
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, ]
Por supuesto, podría manejar el primer elemento fuera del ciclo. Esto se ve más limpio, pero conduce a una duplicación parcial del código. Además, no será tan fácil hacer esto cuando se trabaja con resumen
iterable
:
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 realizar una comprobación
i == 0
(solo funciona para determinar el primer elemento, no el último), sin embargo, la mejor solución sería un generador que devuelva el
first
y
last
indicador con el 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
Ahora la función original podría 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 necesita medir el tiempo transcurrido entre dos eventos, use
time.monotonic()
lugar de
time.time()
.
time.monotonic()
nunca cambia en la dirección más pequeña, incluso al actualizar 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 generalmente no saben que están anidados. Puede contarles sobre esto creando gerentes internos usando uno 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()

Cuando necesita pasar información en una cadena de llamadas, lo primero que se le ocurre es pasar datos en forma de argumentos de función.
En algunos casos, puede ser mucho más conveniente modificar todas las funciones de la cadena para transferir una nueva pieza de datos. En su lugar, puede especificar un contexto que será utilizado por todas las funciones en la cadena. Como hacerlo
La solución más fácil es usar una variable global. En Python, también puede usar módulos y clases como guardianes de contexto porque, estrictamente hablando, también son variables globales. Probablemente ya lo haga regularmente, por ejemplo, para escribir en un diario.
Si su aplicación es multiproceso, las variables globales normales no funcionarán para usted, ya que no son seguras para subprocesos. En cada momento, puede tener varias cadenas de llamadas y cada una de ellas necesita su propio contexto. El módulo de
threading
lo ayudará, proporciona un objeto
threading.local()
, que es seguro para subprocesos. Puede almacenar datos en él con un simple acceso a los atributos:
threading.local().symbol = '@'
.
Sin embargo, ambos enfoques descritos no son seguros para la concurrencia, es decir, no son adecuados para la cadena de llamadas Coroutine, en la que el sistema no solo llama a funciones, sino que también espera que se ejecuten. Cuando se ejecuta una corutina en
await
, un flujo de eventos puede desencadenar otra corutina de una cadena diferente. Esto 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 solucionar esto forzando el ciclo para establecer y restaurar el contexto cada vez que cambie entre las rutinas. Puede implementar este comportamiento utilizando el módulo
contextvars
, que ha estado disponible 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'), ))