VisasQ Dev Blog

ビザスク開発ブログ

Pylance (type hints) の裏側を調べてみた

こんにちは、Core SRE の西川(@taxin_tt)です。普段はSREチームの一員として、インフラ周りの整備やたまにコードを書いたりしています。

ビザスクのアプリケーションのバックエンドはPythonで書かれています。普段の開発ではVS Codeを利用しているのですが、言語サポート用にPylanceという拡張機能を入れています。

PylanceはMicrosoftが出しているPythonの言語サポートを提供する拡張機能で、下記のような機能を有しています。

  • Docstrings
  • Signature help, with type information
  • Parameter suggestions
  • Code completion
  • Auto-imports (as well as add and remove import code actions)
  • etc...

お世話になっているのが type informationの機能で、コードに対してカーソルを当てることで型情報を表示してくれます。ユーザー側で書いたコードについては、型アノテーションを使って引数や変数に対して明示的に型を記述することで型情報が表示できます。

requestsなどの標準ライブラリを利用する際も同様に型情報を記述・表示できます。一方で、VS Codeでrequestsのライブラリに紐づく型情報 (requests.sessions._Data) に対してコードジャンプすると dist/typeshed-fallback/stubsディレクトリ配下にあるファイルに飛ばされるなど、裏側の仕組みがピンときてなかったので、それを調べるのが今回の内容です。

def post(
        self,
        path: str,
        *,
        data: requests.sessions._Data | None,
        timeout: requests.sessions._Timeout | None,
    ) -> requests.Response:
        return requests.post(
            f"{self.base_url}{path}",
            headers=self.headers,
            data=data,
            timeout=timeout,
        )


type hintsとstub file (pyi) について

前提の話として、type hintsや型アノテーションについてはPEP 484 - Type Hintsの内容がベースになっています

peps.python.org

型定義の構文に関する説明もありますが、その中にstub fileに関する説明があります。
stub fileは型ヒント (type hints) を含むファイルのことを指し、コード実行時ではなく静的型チェックを行うツールによってのみ利用されるという特徴があります。代表的な型チェックを行うツールとしてはmypyなどが挙げられます。

www.python.org

また、対応する実際のライブラリモジュールと同じディレクトリでstub fileを保持できるように .pyiという拡張子を使います。


typeshedとは?

先程のサンプルコードに戻って、VS Codeでコードジャンプをするとstub fileに飛んで型情報を見れますが、ディレクトリパスに表示されている typeshed (typeshed-fallback)は一体何でしょうか?

typeshedとは、標準ライブラリや組み込み型のstub fileのコレクションのことで、READMEにも下記のような説明があります。

Collection of library stubs for Python, with static types

OSSとしてGitHubに公開されていることもあり、Contributerによって実装されたサードパーティーのlibraryの型情報も含まれており、typeshedを通じて標準ライブラリやThird partyのライブラリの型情報が配布されているということになります。


typeshedとPylanceの関係性

ここで、typeshedとPylanceの関係性について改めて整理します。

Pylanceが裏側で利用しているtype checking toolがPyrightというもので、Pyright ではTypeshedに実装されているstdlib (標準ライブラリ) のstub fileの最新版のコピーが含まれています

github.com

Pyright includes a recent copy of the stdlib type stubs from Typeshed. 
It can be configured to use another (perhaps more recent or modified) copy of the Typeshed type stubs. 
Of course, it also works with custom type stub files that are part of your project.

つまり、ユーザーはPylanceを利用することで、VS CodeがPyrightにbundleされたTypeshedで定義されているstub fileを利用してコード上でtype hintsを表示しているということです。

コードジャンプした際に表示されたファイルの正体は、Typeshedのstub file (dist/typeshed-fallback/stubs/requests/requests/sessions.pyi) でした。


mypyでの型チェックとtypeshed

先程、型チェックを行うツールの一例としてmypyを上げましたが、mypyはtypeshedをどのように使っているのでしょうか?

https://mypy.readthedocs.io/en/stable/stubs.html を見ると下記のような記述があり、mypyもtypeshedを利用していることがわかります。

Mypy uses stub files stored in the typeshed repository to determine the types of standard library and third-party library functions, classes, and other definitions.

実際に、.mypy_cache 配下でrequestsライブラリ用のディレクトリを見てみると、一連のstub fileとmypyで利用するmetadataをjson形式で保持しています。
(ここでは、requests.sessionsを例に挙げています。)

// sessions.data.json
{
    ".class": "MypyFile",
    "_fullname": "requests.sessions",
    "future_import_flags": [],
    "is_partial_stub_package": false,
    "is_stub": true,
    "names": {
        ".class": "SymbolTable",
        "Any": {
            ".class": "SymbolTableNode",
            "cross_ref": "typing.Any",
            "kind": "Gdef",
            "module_hidden": true,
            "module_public": false
        },
...
// sessions.meta.json
{
    "data_mtime": 1664257188,
    "dep_lines": [
        8,
        8,
        ...
    ],
    "dependencies": [
        "requests.adapters",
        "requests.auth",
        "requests.compat",
        ...
        "urllib3.util",
        "urllib3.util.retry"
    ],
    "hash": "0260ce4c1c008f12576270febc01c4c9e85ff960648b637d1be7cc632b74e9c5",
    "id": "requests.sessions",
    "ignore_all": true,
    "interface_hash": "f16a17468650240d93dfd8a9f4436c5ef6b3206b43288dbbd910332fe5658070",
    "mtime": 1664257062,
...

上記のファイルをmypy側でparseして、type checkで利用するという実装になっているようです。
(Typeshedのstub fileがどのようにして、上記のようなmypyで利用されるfileに変換されるかといった詳細な実装までは追えていません。)


終わりに

VS Code拡張機能であるPylanceの実装から、type hintsの詳細やtypeshedがどのような役割を果たしているかについて調べました。
普段、何気なく利用しているツールでも実装などの裏側を調べてみると、思わぬ気づきがあって面白いかもしれません。