8.12. Gestores de contexto asíncronos#
La Introducción a Python presentó los gestores de contexto – los objetos que controlan los bloques with, con __enter__ ejecutándose a la entrada y __exit__ ejecutándose a la salida sin importar cómo termine el bloque. Los gestores de contexto asíncronos son la misma idea trasladada a asyncio: __aenter__ y __aexit__ son corrutinas, de modo que la configuración o la limpieza pueden hacer await de algo. El bloque que los controla es async with.
Ya hemos usado uno. asyncio.Lock es un gestor de contexto asíncrono; la página de locks lo usó como async with bus_lock: .... Esta página trata sobre escribir uno para el propio recurso de una aplicación.
8.12.1. Cuándo escribir uno#
Cuando la aplicación tiene un recurso que necesita configuración y desmontaje emparejados, y al menos uno de esos lados tiene que hacer await de algo. Conexiones de red, sensores que necesitan un retardo de estabilización tras la configuración, cualquier cosa que bloquee algo a la entrada y lo desbloquee a la salida. La forma síncrona simple with no aplica porque sus __enter__ y __exit__ no pueden ser corrutinas.
8.12.2. Los dos métodos#
async def __aenter__(self)se ejecuta cuando se entra en el bloque. Lo que retorna es aquello a lo que se vincula la cláusula opcionalas namedeasync with. Retornarselfes la forma más común, pero cualquier valor funciona.async def __aexit__(self, exc_type, exc, tb)se ejecuta cuando se sale del bloque.exc_typeesNoneen una salida normal; en una excepción (o una cancelación) es la clase de la excepción, yexces la instancia. Retornar un valor verdadero le dice a Python que la excepción ha sido manejada y no debe propagarse. RetornarNone(el caso habitual) deja que la excepción continúe hacia arriba por la cadena de llamadas después de que se ejecute la limpieza.
8.12.3. Un ejemplo trabajado#
Un envoltorio de foco que enciende un LED durante el cuerpo del bloque, con un breve retardo de estabilización para que la iluminación sea estable antes de que ocurran las capturas, y vuelve a apagar el LED a la salida:
import asyncio
from machine import LED
class Spotlight:
def __init__(self, led):
self._led = led
async def __aenter__(self):
self._led.on()
await asyncio.sleep_ms(50)
return self
async def __aexit__(self, exc_type, exc, tb):
self._led.off()
async def main():
led = LED("LED_WHITE")
async with Spotlight(led):
# work that benefits from steady illumination
await asyncio.sleep_ms(200)
asyncio.run(main())
Cuando se entra en el bloque, se ejecuta __aenter__: el LED se enciende, el await de estabilización de 50 ms cede el control al bucle para que otras corrutinas puedan avanzar mientras tanto, y el cuerpo del bloque comienza una vez que la espera se completa. Cuando el bloque termina – en una salida normal, en una excepción o en una cancelación – se ejecuta __aexit__ y el LED se vuelve a apagar. La limpieza se ejecuta en todos los casos; esa es la garantía que async with proporciona.
La página de captura de fotogramas muestra cómo hacer que csi.CSI.snapshot() sea compatible con await; una vez que se tiene ese envoltorio, el cuerpo de un bloque async with Spotlight(led): sería normalmente un bucle de captura ejecutándose bajo la iluminación constante.