Python 3.10 の問題: 内部変更によりバグが露呈 (PEP 626 および関連)
By JoeVu, at: 2025年8月6日15:01
Estimated Reading Time: __READING_TIME__ minutes
![[Python 3.10 issues] Internal Changes Exposed Bugs (PEP 626 & friends)](/media/filer_public_thumbnails/filer_public/3b/ec/3bec560d-bee3-4ee5-891b-5f4b2579170a/python_310_issues_internal_changes_exposed_bugs_pep_626__friends.png__1500x900_q85_crop_subsampling-2_upscale.jpg)
![[Python 3.10 issues] Internal Changes Exposed Bugs (PEP 626 & friends)](/media/filer_public_thumbnails/filer_public/3b/ec/3bec560d-bee3-4ee5-891b-5f4b2579170a/python_310_issues_internal_changes_exposed_bugs_pep_626__friends.png__400x240_q85_crop_subsampling-2_upscale.jpg)
考えられる症状
-
デバッガー/プロファイラー/トレーサーの異常な動作(ブレークポイントがラインをスキップする、カバレッジが一致しない)。
-
バイトコードオフセットに依存するツールが壊れる(例:`frame.f_lasti`に関する想定)。
-
フレーム/バイトコードを検査するカスタムインストルメンテーションの不正確さ。
原因
Python 3.10は正確な行番号テーブル(PEP 626)を採用し、インタープリターがバイトコードをソースラインにマッピングする方法を変更しました。これに伴い、以下のようになります。
-
`frame.f_lasti`は、実際には生のバイトオフセットではなく命令オフセットになりました(インタープリターは以前からワードコードに切り替わっていましたが、3.10でセマンティクスが厳密化されました)。詳細は、3.10の新機能 → トレースバックと行番号 および フレームオブジェクトのドキュメント を参照してください。
-
行番号テーブルは、例外とトレースに対してより正確になりました。これはユーザーにとっては素晴らしいことですが、古い非公式な動作に依存していたツールを壊します。
修正/移行パターン
1) 内部バイトコードの詳細に依存せず、サポートされているAPIを使用してください。
-
オフセットのハードコーディングではなく、命令/オフセットを理解するために dis モジュールを優先してください。
-
ソースロケーションについては、内部を自分でデコードするのではなく、`frame.f_lineno` とコードオブジェクトの属性(`co_filename`、`co_firstlineno`、3.11以降の`co_positions()`)に依存してください。 inspect を参照してください。
2) PEP 626セマンティクスに合わせてトレース/デバッグロジックを更新してください。
-
`sys.settrace`/`sys.setprofile` を実装している場合、より多くのトレースイベントと、より正確な行ホップが期待できます。 トレーシングフック のドキュメントを確認し、カバレッジを過剰にカウントしたり、分岐を見逃したりしないようにフィルターを調整してください。
3) 可能であれば、より高レベルのライブラリを使用してください。
-
コードの書き換え/解析については、バイトコードのポッキングよりもAST/LibCST を優先してください。バイトコードは安定したインターフェースではありません。ASTがそうです。
4) C拡張機能およびフレーム/バイトコード統合を再構築して再テストしてください。
-
C拡張機能がフレームの内部やコードオブジェクトに触れる場合は、3.10で再コンパイルし、3.10のC-APIの変更 に対して監査してください。
例:バイトオフセットを想定していたトレースツール
# 以前:生のf_lasti(バイトオフセットのように扱われていた)を比較
def trace(frame, event, arg):
if frame.f_lasti in SOME_BYTE_OFFSETS: # 壊れやすい
...
return trace
# 現在:disを使用して命令オフセットを堅牢にマッピングする
import dis
def trace(frame, event, arg):
code = frame.f_code
instrs = list(dis.get_instructions(code))
# 生の数値ではなく、(offset, starts_line)またはopname/nameパターンで一致させる
...
return trace
次回以降、痛みを避ける方法
-
バイトコードは不安定なものとして扱ってください。検査する必要がある場合は、`dis` を使用し、オフセットのシフトに対してロジックが回復力を持つようにしてください。
-
早期に新しいPythonバージョンでCIを追加してください(アルファ/ベータ版)、これによりトレース/カバレッジツールがGA前に変更を表面化させます。
-
バージョン間の移植性のために、AST/ソースレベルのインストルメンテーションを優先してください。