【Python】私のissueがきっかけでPEP585が改訂されたお話【型アノテーション】

## 前置き
私はPythonでCOMオブジェクトを操作するライブラリ[`comtypes`](https://github.com/enthought/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`には下記のような「ブリッジ」が書かれることがあります。
```py
if sys.version_info >= (3, 0):
    text_type = str
else:
    text_type = unicode
```

アノテーションも同じように、バージョンごとでやり方に違いがあります。
`2.7`では`foo: int = 1`や`def foo(bar: int):`のようなランタイムコード上でのインラインアノテーションや関数アノテーションは`SyntaxError`となるため、[PEP484で提唱されたコメントによる型アノテーション](https://peps.python.org/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code)をしなければいけません。
それでもコメントによるアノテーションは現在に至るまでサポートされているため、ブリッジを書くこともなく、普段プロダクトを開発する際にやっていることと勝手が違うぐらいで特に問題ではありませんでした。

一方で、あるバージョンからdeprecatedになるものが型アノテーション関係にもありました。
それがビルトインのコレクション型でもジェネリクスとして使用できるようにする`PEP585`でした。

## `PEP585`って?
詳しく説明している記事はいくらでもあるので、ここではコードスニペットでざっくりした説明だけします。
```py
# 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`公式ドキュメント](https://docs.python.org/ja/3.9/library/typing.html#module-contents)にはこう書かれています。
> 注釈 このモジュールは、既存の標準ライブラリクラスのサブクラスかつ、 [] 内の型変数をサポートするために 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`型のように"なくなって"しまうんだ」
- 「ランタイムの分岐と同じように、アノテーション用のシンボルもブリッジをしなきゃいけなくなるんだ」
  - ```py
    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のリポジトリで相談しました。

https://github.com/microsoft/pylance-release/issues/3066

原因としてはキャッシュの状態かなにかによるものだったので、設定ファイル周りをいったん更新したり再起動したりで治りました。

しかし、issueに回答してくれた、`typing`のコントリビューターでもあるMicrosoftテックフェロー[erictraut](https://github.com/erictraut)さんから「好奇心からの質問」があり、やり取りをしました。
- 原文はissueを参照してください。

> [ericさん:](https://github.com/microsoft/pylance-release/issues/3066#issuecomment-1201862763)
> 好奇心からですが、なぜタイピングシンボルを抽象化しようとしているのですか？
> 型付け記号がなくなることはないでしょう。それを使っているPythonのコードがあまりにも多く、その削除はあまりにも破壊的です。
> その使用を避け、ボイラープレートや互換性モジュールで抽象化する必要はありません。

> [私:](https://github.com/microsoft/pylance-release/issues/3066#issuecomment-1201870063)
> `> 型付け記号がなくなることはないでしょう`
> 本当に？
> `typing` のドキュメントには取り除かれるとありました。
> `> 公式ドキュメントからの引用`
> そしてPEP585にはこうあります。
> `> PEPからの引用`
> それともこの時点でYAGNIということになるのでしょうか？

> [ericさん:](https://github.com/microsoft/pylance-release/issues/3066#issuecomment-1201871901)
> `> 本当に？`
> ええ、この文脈での「非推奨」は、後方互換性の要件がないコードに使用できる新しい推奨アプローチがあることを意味します。
> これらのシンボルが削除されるわけではありません。これらのシンボルを`typing`から削除するのは、あまりに混乱を招くでしょう。

> [私:](https://github.com/microsoft/pylance-release/issues/3066#issuecomment-1201891190)
> あなたのPythonの知識に敬意を表します。
> `> これらのシンボルを...削除...`
> まさに私が誤解していたところです。
> 私はためらうことなく `typing` を使用することにします。
> このissueは解決済みで結構です。
> ありがとうございました。

## 2つめのissue
そうはいっても現在のドキュメントや関連記事を見ているうちに不安になったため、思い切って`python`オーガナイゼーションの`typing`リポジトリまで質問しに行きました。

https://github.com/python/typing/issues/1230

そこでも「`typing`からシンボルはなくならない」ということをPython core developerたちから聞くことができました。

## `discuss.python.org`へのエスカレーション

実はこの問題はPython本体のdiscussionでも取り上げられていましたが、議論が停滞していました。
しかし私のissueをきっかけにして再び動き出しました。

https://discuss.python.org/t/concern-about-pep-585-removals/15901/9

## 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されているので、随時周辺のドキュメントもアップデートされていくのでしょう。

https://github.com/python/peps/pull/2778

## 備考
`typing`からジェネリクスが削除されなくても、Python core developerたちは「特に問題がなければビルトインのジェネリクスを使ったほうがいい」と考えています。
つまり、もし`Python 3.9`以降でしかプロダクションコードを書かないのであれば、`typing.Tuple`ではなく`builtins.tuple`を使うことが推奨されています。

## 教訓
「不安だと思ったら、とりあえず聞いてみよう、たぶん無下に扱われはしないから」