きっかけ
私が参画しているプロジェクトでは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を立ててあり、そのうち修正してもらう予定になっています。
(上記が解決しだい)みなさんのコントリビュートもお待ちしております。
