Python 3.15: contextmanager をジェネレータ・async 関数のデコレータにしたときの挙動が変わる
@contextmanager / @asynccontextmanager で作ったものを、yield を含む関数や async def のデコレータとして使っていると、3.14 までは本体が動く前に with が閉じていた。3.15 からは反復中・await 中も開いたままになる。
何が変わる?
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が終わるまで開いたまま。
「本体の実行中に開いていてほしい」という本来の意図どおりになるので、多くの場合はバグが直る方向。ただし、前の挙動に合わせて書いたコード(ロックやトランザクションを短く持つ前提など)は動きが変わる。
上げる前の確認
- デコレータとして使っている箇所を探す(読むだけで何も変えない)
sh
grep -rnE "ContextDecorator|AsyncContextDecorator|contextmanager|asynccontextmanager" --include=*.py . - 見つかったもののうち、
@xxx(...)の形で ジェネレータ関数・async def・非同期ジェネレータ に付けているものを確認する。普通の関数に付けている場合や、with xxx():で使っている場合は影響しない。 - 3.14 と 3.15 の両方でテストを流し、開く・閉じるのタイミングに依存したテスト(ログの順番、ロックの解放など)を確かめる。
注意点
- 非同期ジェネレータに付けた場合の
asend()で送った値の扱いが 3.15.0 で変わったという検証報告もある(出典の投稿)。非同期ジェネレータに付けているなら、値を送る使い方を特に確認する。 - 自作の
ContextDecoratorサブクラスも同じく影響を受ける。
出典
- @tane_log · 2026-10-20 (確認用の grep: @tane_log · 2026-10-20 )
- What's New In Python 3.15(contextlib, gh-125862): docs.python.org