Python 3.x 系:互換性維持で注意すべき点

以前「Ruby 4.0 新機能と Ruby 3.x 系コードの互換性維持で注意すべき点」をまとめたが、Python においても 3.x 系の中で最新の 3.14 に至るまで、互換性維持のために注意すべき点がある。
本稿は、Python 3.1 から Python 3.14 までを俯瞰し、実務で境界になりやすい「互換性破壊ポイント」を整理したうえで、各マイナーバージョンの「後方互換性を壊しうる変更」を一覧表に落とし込む。
公式には後方互換性の扱いは PEP 387 に整理されているが[1]、実際の移行作業は「文法」「標準ライブラリ」「警告→削除のタイムライン」「周辺ツール(パーサ、型ヒント、ビルド)」の複合になる。

前提:Python は「互換性は原則守るが、非推奨(Deprecation)→削除(Removal)を段階的に行う」モデルで進む[1]
そのため、保守観点では「警告が出ている箇所」と「次の削除予定」を早期に把握し、境界を越える前に潰すのが最も効率がよい。
公式の各バージョンの変更点は “What’s New in Python” にまとまっている[2]


1. 実務で境界になりやすい互換性破壊ポイント

1.1 文法(予約語・構文追加)で「昔の書き方」が突然 NG になる

Python 3.x は言語の大枠は保守的に進むが、それでも「予約語化」や「新構文」により、過去に合法だった識別子が使えなくなる局面がある。
典型は async/await の予約語化(Python 3.7 以降)で、変数名や引数名に async/await を使っていた古いコードが文法エラーになる[3]
もう一つの代表例は Python 3.10 の構造的パターンマッチ(match/case)で、match/case は “soft keyword” として導入されたが、特定の位置ではキーワードとして解釈されるため、コードジェネレータや DSL 系の字句処理が影響を受ける可能性がある[4][5]

1.2 標準ライブラリの「非推奨→削除」が、想定より後で効いてくる

互換性破壊は文法よりも、標準ライブラリの削除で起きることが多い。
Python 3.12 では distutils が標準ライブラリから削除され、セットアップ周辺の古い実装が動かなくなる[6][7]
Python 3.13 では PEP 594 により “dead batteries” とされた複数モジュールが削除され、運用スクリプトが標準ライブラリ依存だった場合に直撃する[8][9]
さらに 2to3 / lib2to3 の削除も同じ系統で、変換ツール自体だけでなく「lib2to3 をパーサとして組み込んでいた」周辺ツールにも影響しうる[9]

1.3 型ヒント(アノテーション)の「実行時挙動」を前提にすると壊れる

型ヒントは静的解析向けの仕組みだが、実務では「実行時にアノテーションを読む」系が普及した結果、アノテーション評価の仕様変更が互換性問題になりやすい。
“Postponed Evaluation of Annotations” は PEP 563 として整理され[10]、その後の流れを踏まえて Python 3.14 では PEP 649 に基づく “deferred evaluation” が導入されている[11][12]
「__annotations__ を直接読んで文字列が入っている前提」や「評価タイミングを暗黙に期待する」コードは、バージョン境界で壊れやすい。

1.4 “警告を無視していると、2 年後に突然落ちる” になりがち

互換性維持のコツは単純で、テスト実行を -W default や -W error で回し、警告を「将来の破壊点の予告」として扱うことだ。
“What’s New” でも「警告を確認しろ」という誘導が繰り返し書かれている[13]
逆に言えば、警告を CI で見ていないと、標準ライブラリ削除(3.12/3.13)や移動(collections.abc)などの “期限切れ” を踏む。
collections の ABC 移動は「deprecated から数年かけて削除」された典型で、PEP 606 でも例示されている[14]


2. 互換性維持の実装指針(保守のやり方)

2.1 方針を 2 層に分ける

互換性維持を「全スクリプトで Python 3.1 まで完全互換」にするのではなく、方針に沿って “fully supported” と “partial compatibility” を分けるのが現実的である。
fully supported(例:Python 3.6+)は CI で毎回回す。
partial(例:3.1〜3.5)は「互換性を壊す変更を入れない」ことを優先し、必要なら互換レイヤ(条件分岐・フォールバック)を明示する。

2.2 実務上の境界に合わせた最小チェック項目

境界 壊れ方(代表例) 検出・回避の最小策
3.6 f-string を使い始めると 3.5 以前で即 SyntaxError(PEP 498) 文字列フォーマットは format / % を残す。f-string は fully supported 範囲内でのみ許可。[15]
3.7 async/await が予約語化し、識別子として使っていたコードが壊れる 変数名の衝突を lint で検出。古いコード取り込み時は機械置換を先にかける。[3]
3.9 collections の ABC エイリアスが削除され、collections.Mapping 等が ImportError collections.abc から import する。移行期は警告をエラー化して早期発見。[14]
3.10 match/case(構造的パターンマッチ)により、パーサ依存ツールや DSL のトークン化が破綻しうる 文字列テンプレートや AST 変換で “match/case をキーワードとして扱うか” を明示し、lib2to3 依存を避ける。[4][5]
3.12 distutils 削除でビルド/配布まわりが壊れる setuptools 側へ寄せる(distutils 前提のコードは排除)。[6][7]
3.13 PEP 594 のモジュール削除、2to3/lib2to3 削除 該当モジュールの import を棚卸しし、必要なら代替(PyPI パッケージ等)を採用。変換・解析系で lib2to3 を使っていないか確認。[8][9]
3.14 アノテーション評価の仕様が PEP 649 により変更され、実行時に __annotations__ を読むコードが影響を受けうる typing.get_type_hints など推奨 API に寄せる。直接 __annotations__ を読む実装は境界で要テスト。[11][12]

補足:上の表は「壊れやすい境界」を優先している。個別のバージョン差分は次章の一覧表にまとめる。
情報源は “What’s New in Python” 各ページを基準とする[2]


3. 各マイナーバージョンでの「後方互換性を壊しうる変更」一覧(3.1→3.14)

ここでいう「壊しうる変更」は、(a) 文法として実行不能になる、(b) 既存 API が削除・移動される、(c) 仕様の厳密化により例外が増える、のいずれかを指す。網羅は各 “What’s New” に委ねるが[2]、実務で事故になりやすい点に寄せて要約した。3.1〜3.5 は現代のコードベースでは「partial compatibility」の範囲に入りやすいため、主に “これ以上新機能を持ち込むと壊れる” という観点で記す。

バージョン 後方互換性を壊しうる変更(代表例) 実務メモ(保守観点)
3.1
  • (3.0→3.1 の延長として)標準ライブラリ・文法の小さな差分が続くため、3.1 固有の挙動差を前提にしない。
古い系は “動けばよい” に寄せ、モダン機能を持ち込まない(型ヒント、async、f-string 等を禁止)。[16]
3.2
  • 標準ライブラリの挙動修正が多く、境界依存の挙動(bytes/text)に影響が出うる。
文字列/バイト列の扱いは早い段階で統一し、曖昧な混在を避ける。[17]
3.3
  • 標準ライブラリの配置変更が長期的な “deprecated→removal” の起点になる(例:collections の ABC など)。
「古い import が動いてしまう」状態を放置しない。3.3 由来の非推奨は 3.9 などで刈り取られる。[14][18]
3.4
  • 標準ライブラリの追加・整理が進み、後年の置換(pathlib など)で「新 API を使うほど古い版が切れる」構造ができる。
pathlib など “便利だが古い版では使えない” API は、fully supported 範囲でのみ採用する。[19]
3.5
  • async/await(当初は予約語ではなく文脈依存)など、後の予約語化へつながる流れ。
“3.5 では動くが 3.7 以降で壊れる” を避けるため、async/await を識別子に使わない運用を早期に固定する。[20]
3.6
  • f-string(PEP 498)が導入され、3.5 以前との文法互換性が切れる。
fully supported を 3.6+ に置くなら、ここが最初の大きな境界になる。[15][21]
3.7
  • async/await が予約語化し、識別子としての使用が不可になる。
  • アノテーションの評価戦略が議論され、将来の互換性問題の火種になる。
古いコード取り込み時に「async/await の衝突」を最優先で潰す。[3][10]
3.8
  • 標準ライブラリの削除・非推奨の整理が進み、警告を放置すると後でまとめて壊れる。
“API removals” と “Porting” を必ず読む運用にする(この頃から削除が実務の事故要因になる)。[22]
3.9
  • collections の ABC エイリアス(collections.Mapping 等)が削除される。
  • 旧来の互換レイヤ(Python 2 向け)を終端に向けて整理。
import パスの互換レイヤは “いつか消える”。依存箇所は grep で棚卸しして先に直す。[13][14][23]
3.10
  • 構造的パターンマッチ(match/case)の導入。
  • lib2to3 のパーサが新文法についていけず、周辺ツール側で問題が顕在化しうる。
パーサ依存(コード生成・変換)をやっているなら、lib2to3 前提を捨てる判断が必要になる。[4][24]
3.11
  • 非推奨 API の整理が加速し、lib2to3 などの “将来削除” が明確化される。
この版以降は「非推奨の棚卸し」を CI の定常作業に組み込まないと、3.13 で一気に落ちる。[25]
3.12
  • distutils が標準ライブラリから削除される。
配布・ビルド系に distutils を触るコードが残っているなら最優先で排除。[6][7]
3.13
  • PEP 594 により複数の旧モジュールが削除。
  • 2to3 / lib2to3 の削除(3.11 で非推奨→3.13 で削除)。
運用スクリプトは “dead batteries” を使いがちなので、import の棚卸しが効く。[8][9][26]
3.14
  • PEP 649/749 に基づくアノテーションの遅延評価が導入される。
実行時にアノテーションを読むライブラリやユーティリティは、3.14 で挙動差が出うるので回帰テストを置く。[11][12][27]

注:本表は「後方互換性を壊しうる」観点での代表例であり、個々の “Removed / Deprecated” の完全リストは各バージョンの “What’s New” を参照すること。[2][28]


4. 互換性検査ツール(find_pycompat.py)の位置づけ

実務上、互換性事故は「思っていたより新しい機能を混ぜてしまった」ことが原因になりやすい。
find_pycompat.py のように、f-string、subprocess.run、async/await、型ヒント、pathlib、shutil.which などを機械的に検出するアプローチは、“partial compatibility” を維持するうえで有効である。一方で、標準ライブラリ削除(PEP 594、distutils)やアノテーション仕様のように「構文ではなく依存関係・実行時仕様」で壊れるものは別枠で監視が必要になる。[6][8][12]

実務上の提案(最小構成):
fully supported 範囲(例:3.6+)は CI で全テストを実行し、-W error で警告を潰す。
partial compatibility(例:3.1〜3.5)は、find_pycompat.py の検出対象を “禁止リスト” として扱い、混入を止める。
さらに、標準ライブラリ削除系は import 棚卸し(静的 grep+実運用ログ)で二重に見る。


参考文献

  1. PEP 387 – Backwards Compatibility Policy. https://peps.python.org/pep-0387/
  2. What’s New in Python(一覧). https://docs.python.org/3/whatsnew/index.html
  3. What’s New In Python 3.7. https://docs.python.org/3/whatsnew/3.7.html
  4. What’s New In Python 3.10. https://docs.python.org/3/whatsnew/3.10.html
  5. PEP 634 – Structural Pattern Matching: Specification. https://peps.python.org/pep-0634/
  6. What’s New In Python 3.12. https://docs.python.org/3/whatsnew/3.12.html
  7. PEP 632 – Deprecate distutils module. https://peps.python.org/pep-0632/
  8. PEP 594 – Removing dead batteries from the standard library. https://peps.python.org/pep-0594/
  9. What’s New In Python 3.13. https://docs.python.org/3/whatsnew/3.13.html
  10. PEP 563 – Postponed Evaluation of Annotations. https://peps.python.org/pep-0563/
  11. PEP 649 – Deferred Evaluation Of Annotations Using Descriptors. https://peps.python.org/pep-0649/
  12. What’s new in Python 3.14. https://docs.python.org/3/whatsnew/3.14.html
  13. What’s New In Python 3.9. https://docs.python.org/3/whatsnew/3.9.html
  14. PEP 606 – Python Compatibility Version(collections ABC aliases removal の例示など). https://peps.python.org/pep-0606/
  15. What’s New In Python 3.6. https://docs.python.org/3/whatsnew/3.6.html
  16. What’s New In Python 3.1(参考:古い差分の確認用). https://docs.python.org/3/whatsnew/3.1.html
  17. What’s New In Python 3.2. https://docs.python.org/3/whatsnew/3.2.html
  18. What’s New In Python 3.3. https://docs.python.org/3/whatsnew/3.3.html
  19. What’s New In Python 3.4. https://docs.python.org/3/whatsnew/3.4.html
  20. What’s New In Python 3.5. https://docs.python.org/3/whatsnew/3.5.html
  21. PEP 498 – Literal String Interpolation(f-string). https://peps.python.org/pep-0498/
  22. What’s New In Python 3.8. https://docs.python.org/3/whatsnew/3.8.html
  23. LWN: Postponing some feature removals in Python 3.9(collections.abc などの削除延期の文脈). https://lwn.net/Articles/811369/
  24. CPython issue: Deprecate lib2to3(背景). https://github.com/python/cpython/issues/84540
  25. What’s New In Python 3.11. https://docs.python.org/3/whatsnew/3.11.html
  26. Python Discuss: PEP 594 implementation notice(3.13 removals). https://discuss.python.org/t/pep-594-has-been-implemented-python-3-13-removes-20-stdlib-modules/27124
  27. Real Python: Python 3.14: Lazy Annotations(解説). https://realpython.com/python-annotations/
  28. CPython docs sources (Doc/whatsnew). https://github.com/python/cpython/blob/main/Doc/whatsnew/3.14.rst

コメントする

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)