8.12. Context manager asincroni

La Panoramica di Python ha introdotto i context manager – gli oggetti che i blocchi with pilotano, con __enter__ eseguito all’ingresso e __exit__ eseguito all’uscita, indipendentemente da come termina il blocco. I context manager asincroni sono la stessa idea trasferita in asyncio: __aenter__ e __aexit__ sono coroutine, quindi la configurazione o la pulizia possono awaitare qualcosa. Il blocco che li pilota è async with.

Ne abbiamo già usato uno. asyncio.Lock è un context manager asincrono; la pagina lock lo ha usato come async with bus_lock: .... Questa pagina riguarda come scriverne uno per una risorsa propria di un’applicazione.

8.12.1. Quando scriverne uno

Quando l’applicazione ha una risorsa che richiede configurazione e smantellamento accoppiati, e almeno uno di questi due lati deve awaitare qualcosa. Connessioni di rete, sensori che necessitano di un ritardo di stabilizzazione dopo la configurazione, qualsiasi cosa che blocchi qualcosa all’ingresso e lo sblocchi all’uscita. La forma sincrona ordinaria with non è applicabile perché i suoi __enter__ ed __exit__ non possono essere coroutine.

8.12.2. I due metodi

  • async def __aenter__(self) viene eseguito quando il blocco viene avviato. Qualunque cosa restituisca è ciò a cui viene associata l’opzionale clausola as name di async with. Restituire self è la forma più comune, ma qualsiasi valore va bene.

  • async def __aexit__(self, exc_type, exc, tb) viene eseguito quando si esce dal blocco. exc_type è None in caso di uscita normale; in caso di eccezione (o di annullamento) è la classe dell’eccezione, ed exc è l’istanza. Restituire un valore vero indica a Python che l’eccezione è stata gestita e non deve propagarsi. Restituire None (il caso consueto) lascia che l’eccezione continui a risalire la catena delle chiamate dopo l’esecuzione della pulizia.

8.12.3. Un esempio elaborato

Un wrapper a faretto che accende un LED per la durata del corpo del blocco, con un breve ritardo di stabilizzazione affinché l’illuminazione sia stabile prima che avvengano eventuali acquisizioni, e spegne nuovamente il LED all’uscita:

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())

Quando si entra nel blocco, viene eseguito __aenter__: il LED si accende, l”await di stabilizzazione di 50 ms cede il controllo al loop così che altre coroutine possano avanzare nel frattempo, e il corpo del blocco inizia una volta completata l’attesa. Quando il blocco termina – per un’uscita normale, per un’eccezione o per un annullamento – viene eseguito __aexit__ e il LED si spegne di nuovo. La pulizia viene eseguita in ogni caso; è questa la garanzia che async with fornisce.

The frame capture page shows how to make csi.CSI.snapshot() await-friendly; once that wrapper is in hand, the body of an async with Spotlight(led): block would typically be a capture loop running under the steady illumination.