テック技報最新号へ →
コード

Python 3.15: contextmanager をジェネレータ・async 関数のデコレータにしたときの挙動が変わる

@contextmanager / @asynccontextmanager で作ったものを、yield を含む関数や async def のデコレータとして使っていると、3.14 までは本体が動く前に with が閉じていた。3.15 からは反復中・await 中も開いたままになる。

@tane_log · 2026-10-20
#python#python315#contextlib#migration

何が変わる?

python
from contextlib import contextmanager

@contextmanager
def opened(name):
    print("open", name)
    yield
    print("close", name)

@opened("db")          # デコレータとして使う
def rows():
    yield 1
    yield 2

for r in rows():
    print(r)
  • 3.14 まで: rows() を呼んでジェネレータオブジェクトができた時点で open → close が終わり、そのあと 1, 2 が出る(本体はコンテキストの外で動く)。
  • 3.15 から: open → 1 → 2 → close の順になる(反復が終わるまで開いたまま)。async def のコルーチンや非同期ジェネレータも同様で、await が終わるまで開いたまま。

「本体の実行中に開いていてほしい」という本来の意図どおりになるので、多くの場合はバグが直る方向。ただし、前の挙動に合わせて書いたコード(ロックやトランザクションを短く持つ前提など)は動きが変わる。

上げる前の確認

  1. デコレータとして使っている箇所を探す(読むだけで何も変えない)
    sh
    grep -rnE "ContextDecorator|AsyncContextDecorator|contextmanager|asynccontextmanager" --include=*.py .
  2. 見つかったもののうち、@xxx(...) の形で ジェネレータ関数・async def・非同期ジェネレータ に付けているものを確認する。普通の関数に付けている場合や、with xxx(): で使っている場合は影響しない。
  3. 3.14 と 3.15 の両方でテストを流し、開く・閉じるのタイミングに依存したテスト(ログの順番、ロックの解放など)を確かめる。

注意点

  • 非同期ジェネレータに付けた場合の asend() で送った値の扱いが 3.15.0 で変わったという検証報告もある(出典の投稿)。非同期ジェネレータに付けているなら、値を送る使い方を特に確認する。
  • 自作の ContextDecorator サブクラスも同じく影響を受ける。

出典

X の元の投稿を開く