8.12. 非同期コンテキストマネージャ¶
Python 概要 ではコンテキストマネージャを紹介しました -- with ブロックが駆動するオブジェクトで、入る際に __enter__ が実行され、ブロックがどのように終わろうとも出る際に __exit__ が実行されます。非同期コンテキストマネージャは、同じ考え方を asyncio に持ち込んだものです。__aenter__ と __aexit__ は コルーチン なので、セットアップやクリーンアップが何かを await できます。それらを駆動するブロックは async with です。
私たちはすでに1つ使っています。asyncio.Lock は非同期コンテキストマネージャであり、ロック ページではそれを async with bus_lock: ... として使いました。このページは、アプリケーション自身のリソース向けにそれを書くことについてです。
8.12.1. いつ書くか¶
アプリケーションが 対になった セットアップとティアダウンを必要とするリソースを持ち、そのどちらか一方が何かを await しなければならない場合です。ネットワーク接続、設定後に安定待ちの遅延が必要なセンサー、入る際に何かをロックし出る際にアンロックするものなどです。素の同期型の with の形は当てはまりません。その __enter__ と __exit__ はコルーチンにできないからです。
8.12.2. 2つのメソッド¶
async def __aenter__(self)はブロックに入ったときに実行されます。それが返すものが、async withの省略可能なas name節がバインドする対象になります。selfを返すのが最も一般的な形ですが、どんな値でも機能します。async def __aexit__(self, exc_type, exc, tb)はブロックを出るときに実行されます。exc_typeは正常に出る場合はNoneです。例外(またはキャンセル)の場合はその例外のクラスであり、excはそのインスタンスです。真と評価される値を返すと、例外が 処理された ので伝播すべきではないと Python に伝えます。Noneを返すと(通常のケース)、クリーンアップの実行後に例外が呼び出しチェーンを上へと進み続けます。
8.12.3. 実践的な例¶
ブロックの本体の間だけLEDを点灯させるスポットライトのラッパーです。キャプチャが行われる前に照明が安定するよう短い安定待ちの遅延を設け、抜ける際には再びLEDを消灯します:
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())
ブロックに入ると __aenter__ が実行されます。LEDが点灯し、50 ms の安定待ちの await がループに制御を譲るので、その間に他のコルーチンが進行でき、待機が完了するとブロックの本体が開始します。ブロックが終わると -- 正常に出る場合でも、例外の場合でも、キャンセルの場合でも -- __aexit__ が実行され、LEDが再び消灯します。クリーンアップはどの場合でも実行されます。それが async with が提供する保証です。
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.