🐍

PEP 765 – Disallow return/break/continue that exit a finally block

に公開

PEP 765 では Python 3.14 から、finally ブロックの中から return / break / continue を使って外に抜けるコードが 非推奨(SyntaxWarningを出すよう) になります。

背景

Python の finallyブロックでは、returnbreak を使うと、そこで例外が再スローされずに握り潰されるという仕様になっています。

example.py
def dangerous():
    try:
        raise ValueError('問題発生!')
    finally:
        return '例外が隠されました'

上記では ValueError が握りつぶされ、関数は 例外が隠されました を返します。例外の隠蔽は大きなリスクです。

また、これらの仕様は公式ドキュメントに記載されていますが、多くの開発者は読んでいません。

  • If the finally clause executes a break, continue or return statement, exceptions are not re-raised.
  • If a finally clause includes a return statement, the returned value will be the one from the finally clause's return statement, not the value from the try clause's return statement.

どれくらい危険な仕様なのか?

PyPI 上で最も人気のある 8000 パッケージ(約1.2億行)を対象に解析したところ、finally 直下に return / break / continue を書いているパターンは100万行あたり 2〜4 件 と稀ではあるものの、その約 7 割が実際のバグに直結していました。

仕様変更の詳細

言語仕様

  • finally から外へ抜ける return / break / continue に対し、コンパイラは SyntaxWarning または SyntaxError を出してよいと定義されました。

CPython 3.14 の対応

  • 現時点では SyntaxWarning のみ発生。
  • 将来的には SyntaxError に格上げされる可能性あり。

該当するコード例

def f():
    try:
        ...
    finally:
        return 42  # ⚠ 警告対象

for item in items:
    try:
        ...
    finally:
        break      # ⚠ 警告対象

対象外のケース(ネストされた関数など)

try:
    ...
finally:
    def g():
        return 42  # OK: finally の外には抜けない

まとめ

PEP 765 により、Python は 潜在的なバグの温床となり得るコードパターン を言語レベルで抑止する方向へ進んでいます。Python 3.14 では警告段階ですが、将来の SyntaxError 化を見据え、finally からの制御フロー逸脱を早めに解消しておくことをおすすめします。
また、Ruffのようなlinter, code formatterを導入しておくと、上記のような問題を素早く検知できます。実際に今回のfinallyブロックの中でreturn文を使っているようなケースもRuffの以下のルールにより検知できます。
return-in-try-except-finally (SIM107)

Discussion