A Day in the Life

Python プロジェクトに型ヒントを導入し、型チェックの恩恵を受ける

最近、手伝っている仕事の機械学習プロジェクトで Python を書き始め、そういえば Python にも型ヒントあったよなぁ、とプロジェクトに導入してみたら簡単に導入でき、かつ静的型チェックの恩恵が受けれてとても便利だった。

しかしながら、型ヒントを書き始めるに当たり「型ヒントについて、どのドキュメントを読めばよいのか」や「どう型ヒントを導入すればよいのか」が最初はイマイチ解らなかったので、その説明とともに Python プロジェクトで型を書き始める方法を紹介する。

どのドキュメントを読めば良いのか

この2つ読めば、他の静的型付け言語を書いたことがある人なら、まずは十分。

  • Understanding Typing
    • 型チェッカー実装である、pyright のドキュメントの一部。ポイントが簡潔にまとまっている。
  • typing --- 型ヒントのサポート
    • Python のオフィシャルドキュメント。分かりやすいというよりは、リファレンスマニュアル感が強いので、Understanding Typing を読んだ後に一通り読むと良い。

この2つを読むと、Python の型ヒントが何となく解り、型ヒントを書いてみたくなりましたよね(?)。また、あまりコード良い例では無いかも知れないが、社内勉強会で紹介した型挙動確認のサンプルコードはこんな感じ。

vscode-1

どう型ヒントを導入すればよいか

型ヒントをプロジェクトに導入するにあたり、まず考えなくてはならないのが型ヒントチェッカー実装の選定。Python の型ヒントの仕様は PEP でいくつも策定されていて、それを具体的に実装しているツールがいくつもある。メジャー所でも以下が挙げられる。

どの実装も Language Server Protocol で動かすことができ(pytypeは未確認)、LSP 対応 IDE やエディタから利用ができる。Python 3以降で良いなら個人的なオススメは pyright。pyright は動作速度も速く、github の issue/PR も Microsoft 社員がアサインされているためかほぼ即レスで対応も速い。また VSCode だと pylance 拡張を入れることで、簡単に利用することができる。

なお pylance の罠として、初期設定は typecheck が off というマジか…という設定(pyrightの標準は basic)なので、VSCode の settings.json で変更するべし。でないと、pylance 入れたからこれで型ヒント書ける、エラー出てないし問題ない、と思いきや、ただ型チェックされてないだけだったということに陥る(というか当初の自分が陥った)。

  // settings.json
  // 現在初期値は "off" となっている🤣なんでや~
  "python.analysis.typeCheckingMode": "basic",
  // workspace 全体に対して型チェック。
  "python.analysis.diagnosticMode": "workspace"

標準はoffなんだぜ!?

なお、pylance は型チェッカーである pyright(OSS) を内包しているのだけど、pylance 自体はその他多機能も含むためOSSでは無いことに注意。VSCode 以外の人は pylance でなくて pyright を使えば問題がないので、pylance が無いからといってとりわけ困ることはない、ハズ。

pyright をプロジェクトに導入する

cli として pyright を入れるだけなら Node のパッケージマネージャ npm 経由でインストール後、pyright コマンドを使うだけ。

$ npm install --global pyright
$ pyright
No configuration file found.
pyproject.toml file found at C:\Users\hotch\src\github.com\....
Loading pyproject.toml file at C:\Users\hotch\src\github.com\...\pyproject.toml
Assuming Python platform Windows
No include entries specified; assuming C:\Users\hotch\src\github.com\...
Auto-excluding **/node_modules
Auto-excluding **/__pycache__
Auto-excluding .git
stubPath C:\Users\hotch\src\github.com\...\typings is not a valid directory.
Searching for source files
Found 62 source files
0 errors, 0 warnings, 0 infos
Completed in 2.591sec

型エラーがあれば検出されるし、pyright -w でファイル監視&常時起動にしたりもできる。ただ、通常は VSCode やエディタが pyright を通した型チェックを行うはずで、pyright コマンドを使う機会は少ない。

また、プロジェクトのルートディレクトリに pyproject.toml もしくは pyrightconfig.json ファイルを置いて設定を記述ることで、プロジェクト全体に設定の適用ができる。

# pyproject.toml
# https://github.com/microsoft/pyright/blob/main/docs/configuration.md
[tool.pyright]
pythonVersion = "3.7"
typeCheckingMode = "basic"

個人的には pyright のバージョンを固定したかったり、npm install でサクッと入れたかったりするので、プロジェクトに Node のパッケージ管理用の package.json を置いてしまっている。

{
  "name": "pyright-exec",
  "version": "1.0.0",
  "description": "",
  "main": "",
  "scripts": {
    "pyright": "pyright"
  },
  "author": "",
  "license": "",
  "dependencies": {
    "pyright": "^1.1.155"
  }
}

CI上で実行したければ、たとえば Github Actions にこんな感じで記載()して、型チェックを実行できる。

      - uses: actions/setup-node@v1
        with:
          node-version: 14.x
      - name: Install node dependencies
        run: npm install
      - name: Typecheck
        run: npm run pyright

最初は pyright、Node で書かれているし Python プロジェクトに入れるのはなぁ、と思ったのだけど依存ライブラリが0個(大抵はやたら依存ライブラリが入れられてしまうのだが)のため即インストールできて、そういう点でも快適だ。

Python のバージョンと型ヒント

さて、早速 Python で型ヒントを書き始めて…となると次にハマるのが、Python のバージョンによって使える型周りが異なること。例えば list の型ヒントを定義したいと思っても、以下のように挙動が異なる。

# py 3.9~ 何もしないで builtins のものとして書ける
l: list[str] = []
# py 3.7~は future import でバックポートを実現
from __future__ import annotations
l: list[str] = []
# py 3.5~ は typing モジュールを使う
# なおこの書き方は 3.9 ~非推奨に…
from typing import List
l: List[str] = []

また、よく使う型モジュールに TypedDict があるのだけど、これは Python 3.8 以降でのサポート。

# py3.8~
from typing import TypedDict

使いたい Python バージョンの typing モジュールにない場合は typing_extensions パッケージをインストールして使うことで、これまた大抵のものはバックポートして使える。

# py3.7以前の場合。もちろん3.8以降でも動く。
from typing_extensions import TypedDict

この辺の型関連がどの Python バージョンでサポートされているのかは、慣れないうちは逐一リファレンスドキュメントを見て確認する必要がある。また、新規プロジェクトでしがらみがない場合でできるだけ新しい Python を使えばベスト、と思うのだけど、コードを動かす Python 実行環境が古いバージョンのこともあるので注意。具体的には Google Colab が 2021/07 現在 Python 3.7 なので、Colab 上でも動くコードを書くがある場合は 3.7 でも動く書き方をする必要がある。私は最初 3.8 で書いていて、Colab で動かなくて泣く泣く 3.7 に落として書き直した、という経験がある…。

type stub - 別ファイルからの型ヒントを与える

さてさて、続いて型ヒントが書かれていないライブラリを使う必要が出てきてどうしよう、というケースに遭遇する。そんなときはPEP 561で定義されている type stub の仕組みを使って解決する。

pyright では標準設定の stubPath として ./typings が指定されており、このディレクトリ以下に配置されている type stub ファイルである .pyi ファイルが読み込まれる。.pyi をどう書くのかの具体例は stdlib や有名パッケージの type stub が集まっている typeshed を覗くと分かりやすい。なお、typeshed の type stub ファイルは pyright, mypy などに標準で含まれているため、とりわけ別途インストールする必要がない。

また、pyright は type stub の雛形を生成する pyright --createstub packagename コマンドがあったり、pylance の UI 経由で作ったりも可能。詳しくは pyright のドキュメント Type Stub Filesで。

型ヒントを書き始めよう

型ヒントを書くことで、IDEの補完各種やリファクタリングといった恩恵が受けれて開発効率が上がったり、静的型チェックで型が通る安心感、実行時エラーの減少等々、受けれるメリットは多い。また Python の型ヒントは、例えば TypeScript の型システムと比べるとできることが圧倒的に少なく、もっといい感じに型操作したいと思うこともあるのだけど、それ故にシンプルな型ヒント記述がほとんどになるので、学習コストが低く誰が見てもたいてい分かりやすい型ヒントになるというメリットも有ると感じる。

プロジェクトに組み込む導入コストも(とりわけ新規プロジェクトなら)低いと思うので、型ヒントを書くか迷っている人は、まず導入してみるのも良いと思う。


その他 PR

2021年7月現在、私は日本経済新聞社イノベーション・ラボで週3-4日、ソフトウェアエンジニアとして機械学習プロジェクト全般を手伝っている(社員ではなく、プロジェクト単位でのお手伝い)。この記事に書いている情報はたいていその業務時間で得られたもので、日経社内勉強会で発表したものを記事形式に書き起こしたものである。

日本経済新聞社は、新聞といった自然言語処理に適した分野はもちろんのこと、日経電子版を通して得られる様々なビッグデータ等々も扱え機械学習関連でも面白い環境だと思うので、興味があれば採用サイトを覗いてみてほしい。また現在、夏季インターンで機械学習系のインターン募集も行っている。

関連するかもエントリー

2021年4月20日
昨晩サウナ行けずじまいだったので、朝から万葉倶楽部に行き温泉 & サウナでととのう。夕方、良い天気だったので近くの公園へノートPCを持っていきプログラミング。家の快適な環境のほうが生産性は高いのだろうけど、心の快適度は外のほうが断然良い。新緑の季節で緑が優しい。夕暮れの帰り道に海...
昨晩サウナ行けずじまいだったので、朝から万葉倶楽部に行き温泉 & サウナでととのう。夕方、良い天気だったので近くの公園へノートPCを持ってい...
Python を学ぶ / 2021年2月18日
昼はアソビルの横濱丿貫。相変わらず美味しい煮干しそば。その後、横浜駅周辺では一番技術書が揃ってるという噂を聞いて、そごう7Fにある紀伊国屋へ。7Fは大きなロフトや無印もあるのね、いままでそごうは地下~1Fまでしか行ったことがなかったので新鮮。そして技術書は噂通り充実していた。とい...
昼はアソビルの横濱丿貫。相変わらず美味しい煮干しそば。その後、横浜駅周辺では一番技術書が揃ってるという噂を聞いて、そごう7Fにある紀伊国屋へ...
2021年7月6日
仕事プロジェクトで、その会社の勉強会で何か話してほしい、とのことだったので Python の型ヒント周りを中心に話す。20分ぐらいで話し終わるかなーと思っていたら、50分ぐらい話してしまってて(60分枠だったので時間的には問題なかったのだけど)ブランクを感じる。タイムキープ周りの...
仕事プロジェクトで、その会社の勉強会で何か話してほしい、とのことだったので Python の型ヒント周りを中心に話す。20分ぐらいで話し終わ...