前置き
私はPythonでCOMオブジェクトを操作するライブラリcomtypes
へのコントリビュートをしています。
その中で型アノテーションを各モジュールにつけていくことを計画しています。
comtypes
はPythonバージョン互換性を持ったライブラリです。
version==1.1.11
時点で2.7
、3.3
、3.4
、3.5
、3.6
、3.7
、3.8
、3.9
、3.10
に対応しています。
これほどまで大量のバージョンをまたいでサポートしていると、「あるバージョンにあったモジュール/クラス/関数があるバージョンからなくなる、若しくは非推奨(deprecated)になる」「逆に追加されたりできるようになる」といったことがよく起こります。
そのため、comtypes
には下記のような「ブリッジ」が書かれることがあります。
if sys.version_info >= (3, 0):
text_type = str
else:
text_type = unicode
アノテーションも同じように、バージョンごとでやり方に違いがあります。
2.7
ではfoo: int = 1
やdef foo(bar: int):
のようなランタイムコード上でのインラインアノテーションや関数アノテーションはSyntaxError
となるため、PEP484で提唱されたコメントによる型アノテーションをしなければいけません。
それでもコメントによるアノテーションは現在に至るまでサポートされているため、ブリッジを書くこともなく、普段プロダクトを開発する際にやっていることと勝手が違うぐらいで特に問題ではありませんでした。
一方で、あるバージョンからdeprecatedになるものが型アノテーション関係にもありました。
それがビルトインのコレクション型でもジェネリクスとして使用できるようにするPEP585
でした。
PEP585
って?
詳しく説明している記事はいくらでもあるので、ここではコードスニペットでざっくりした説明だけします。
# 3.8まではタプルのアノテーションはこのやり方でしか書けなかった
from typing import Tuple
foo: Tuple[str, int] = ("hoge", 0)
# 3.9から`typing`からのimportなしで書けるようになった
foo: tuple[str, int] = ("hoge", 0)
Python3.9時点のtyping
公式ドキュメントにはこう書かれています。
注釈 このモジュールは、既存の標準ライブラリクラスのサブクラスかつ、 [] 内の型変数をサポートするために Generic を拡張している、いくつかの型を定義しています。 これらの型は、既存の相当するクラスが [] をサポートするように拡張されたときに Python 3.9 で廃止になりました。
余計な型は Python 3.9 で非推奨になりましたが、非推奨の警告はどれもインタープリタから通告されません。 型チェッカーがチェックするプログラムの対照が Python 3.9 もしくはそれ以降のときに、非推奨の型に目印を付けることが期待されています。
非推奨の型は、Python 3.9.0 のリリースから5年後の初めての Python バージョンで typing モジュールから削除されます。 詳しいことは PEP 585—Type Hinting Generics In Standard Collections を参照してください。
この文面を読んで、私は
- 「いつの日か、3.9より上のバージョンから
typing.Tuple
のような"ビルトインでも書けるもの"はunicode
型のように"なくなって"しまうんだ」 - 「ランタイムの分岐と同じように、アノテーション用のシンボルもブリッジをしなきゃいけなくなるんだ」
-
if sys.version_info > (3, 9): from builtins import tuple as Tuple else: from typing import Tuple
-
- こういう分岐をいちいち各モジュールのボイラープレートに書いていられないから、型ヒントのシンボルをまとめたモジュールを作ってそこからimportしよう
と考えていました。
最初のissue
そんなわけで型ヒントシンボル用のモジュールを作りimportしたのですが、VSCode
上のpylance
ではTuple
などのシンボルがAny
と解釈されてしまっていました。
恐らくバグだと思ったので、pylanceのリポジトリで相談しました。
原因としてはキャッシュの状態かなにかによるものだったので、設定ファイル周りをいったん更新したり再起動したりで治りました。
しかし、issueに回答してくれた、typing
のコントリビューターでもあるMicrosoftテックフェローerictrautさんから「好奇心からの質問」があり、やり取りをしました。
- 原文はissueを参照してください。
ericさん:
好奇心からですが、なぜタイピングシンボルを抽象化しようとしているのですか?
型付け記号がなくなることはないでしょう。それを使っているPythonのコードがあまりにも多く、その削除はあまりにも破壊的です。
その使用を避け、ボイラープレートや互換性モジュールで抽象化する必要はありません。
私:
> 型付け記号がなくなることはないでしょう
本当に?
typing
のドキュメントには取り除かれるとありました。
> 公式ドキュメントからの引用
そしてPEP585にはこうあります。
> PEPからの引用
それともこの時点でYAGNIということになるのでしょうか?
ericさん:
> 本当に?
ええ、この文脈での「非推奨」は、後方互換性の要件がないコードに使用できる新しい推奨アプローチがあることを意味します。
これらのシンボルが削除されるわけではありません。これらのシンボルをtyping
から削除するのは、あまりに混乱を招くでしょう。
私:
あなたのPythonの知識に敬意を表します。
> これらのシンボルを...削除...
まさに私が誤解していたところです。
私はためらうことなくtyping
を使用することにします。
このissueは解決済みで結構です。
ありがとうございました。
2つめのissue
そうはいっても現在のドキュメントや関連記事を見ているうちに不安になったため、思い切ってpython
オーガナイゼーションのtyping
リポジトリまで質問しに行きました。
そこでも「typing
からシンボルはなくならない」ということをPython core developerたちから聞くことができました。
discuss.python.org
へのエスカレーション
実はこの問題はPython本体のdiscussionでも取り上げられていましたが、議論が停滞していました。
しかし私のissueをきっかけにして再び動き出しました。
PEPの改訂
discussionの結果、PEP585のこの文言は「Python 3.9.0
のリリースから5年後にきっと取り除く(will be removed)」から「取り除くかもしれない、少なくともPython 3.9
のEOL予定の2025年10月より早くはならない(may be removed, Removal will occur no sooner than)」に変わりました。
それでもこういう書き方だと「提示した期日の次の日にやったなら、"早くは"なってないのだから...」というカイジの利根川のようなことを想像してしまいますが杞憂と思いましょう、そんな意地悪な人たちでは絶対ないはずです
PEPリポジトリへのPRもmergeされているので、随時周辺のドキュメントもアップデートされていくのでしょう。
備考
typing
からジェネリクスが削除されなくても、Python core developerたちは「特に問題がなければビルトインのジェネリクスを使ったほうがいい」と考えています。
つまり、もしPython 3.9
以降でしかプロダクションコードを書かないのであれば、typing.Tuple
ではなくbuiltins.tuple
を使うことが推奨されています。
教訓
「不安だと思ったら、とりあえず聞いてみよう、たぶん無下に扱われはしないから」