きっかけ
私が参画しているプロジェクトではCOMをPythonから操作しているスクリプトがあります。
そこで発生するエラーハンドリングを行うため、よく次のようなコードを書いています。
from _ctypes import COMError
try:
...
except COMError as e:
...
ですが、_ctypes
には型スタブがなかったので、importした時点でVSCodeのpylance(pyright)がエラーを出していて、それを抑制したとしてもCOMError
がUnknown
扱いになっていました。
これによって型チェッカーに例外である情報が伝わっていなかったり、COMError
があると型チェッカーから警告が出てやりづらい状況でした。
この状態を長らくしょうがないものとして放置していましたが、そのプロジェクトに参画してから2年近くがたち、いよいよ我慢ならなくなりました。
そこでPythonライブラリの型スタブをまとめているtypeshed
へのコントリビュートを決意しました。
typeshed
について
PEP484に役割が書いてあるのでご参照ください。
https://peps.python.org/pep-0484/#the-typeshed-repo
翻訳はこちら
issueでの議論
さて、いきなりモジュールのスタブを追加する、まして標準ライブラリにあるものを他のコントリビューターへの情報共有もなしにいきなりPRすることは流石にためらわれました。
そこで、issueを立ててでまず私がやりたいことを伝えて、それに関連するものはどういう状況なのかを聞きました。
私のプロジェクトでは、いくつかの COM-ffi パッケージを使用しています。
_ctypes.COMError
や_ctypes.CopyComPointer
も使っていますが、これらは型スタブが定義されていないため、mypy
タイプチェッカーはCannot find implementation or library stub for module named "_ctypes"
エラーを発生させることがあります。このような型スタブを追加したいです。
# githubのissueカンバンにあるスニペット
しかし、これら以外のAPIはよくわかりません。
- そして、
if sys.platform == "win32":
が必要な場所はどこなのか。何かご意見があればお願いします。
その後、typeshed
のコントリビューターから様々なアドバイスが寄せられました。
下記が要約です。
お気軽にPRしていいですよ
stdlib/
以下に_foo.pyi
みたいなモジュールがすでにたくさんあります
CONTRIBUTING.md
を読んで、_ctypes
がincompleteであることをマークしてください- COMの要素はLinux環境では存在していないことを確認したから、COM関係は
if sys.platform == "win32":
の下になります今まで
stdlib/ctypes/__init__.pyi
に定義してきた要素は、実際ランタイムでは_ctypes
に定義されています。もし_ctypes
のスタブが追加されたらランタイムに追随するように移動したほうがいいと思います(とはいっても最初のPRは最小限でOKです)
stdlib/VERSIONS
にも_ctypes
の情報を入れてください
PR mergeまでの顛末
アドバイスを元にスタブを作り、PRしました。
スタブ中のdocstringが不要だったのでそれを取り除くようにレビューを受けて修正されました。
だがしかし、修正後もCIが通らない!
CIのエラーメッセージを見ていると、本来ランタイムで定義されている_ctypes._Pointer
などがないことを怒られていました。
「incompleteなスタブを作ったときには、モジュールレベルでdef __getattr__(name: str) -> Any: ... # incomplete
を定義する」とCONTRIBUTING.md
には書いてあったのにこれはおかしい!
途方にくれていると、レビュアーが諸々修正箇所を指摘してくれました。
stubtest
とallowlist
の存在
stubtest
はmypy
などの型チェッカーにバンドルされているテストで、ランタイム実装の名前空間と型スタブを比べて、型スタブに不足があれば失敗するテストです。
下記issueの"1) Use stubtest to find things missing from or problematic with stubs."に概要が書かれていますが、CONTRIBUTING.md
には書いていなかったので見落としていました。
そんなこんなでレビュアーによってallowlist
にまだスタブに追加していない各オブジェクトが追加されて、CIが通るようになりmergeされました。
現在の状況
mypy
やpylance
にスタブの更新が取り込まれ、Unknown
ではなくなりました!
その後
スタブをランタイムの定義に追随させるべく、コントリビュートを続けています。
その中でどうしても循環importが発生するのでissueを立てて相談しました。
型スタブでは循環importしていてもランタイムとは違ってエラーにならず問題ないと回答をもらったので安心して、少しづつ定義を移動させるPRを出そうとしています。
ただし現在(2022/09/20時点)では_ctypes
に定義を書いてctypes
へimportするスタブを書いても、pytype
を使うテストが失敗してCIが通りません。
これに関してはpytype
側にissueを立ててあり、そのうち修正してもらう予定になっています。
(上記が解決しだい)みなさんのコントリビュートもお待ちしております。