これは、なにをしたくて書いたもの?
以前、Pythonでの型ヒントとMypyについて調べてみました。
Pythonで型ヒント(Type Hints)を試してみる(+Mypy) - CLOVER🍀
もうひとつ押さえておいた方がよさそうな型チェッカーとしてPyrightがあるようなので、こちらも試してみましょう。
Pyright
PyrightのWebサイトはこちら。
GitHub - microsoft/pyright: Static Type Checker for Python
Pyrightを使うこと自体は初めてではなく、PythonのLSP ServerとしてEmacsから使っています。
Emacsにlsp-mode+Python Language Serverをインストールする - CLOVER🍀
この時にインストールしたのがPyrightです。
Pyrightの静的型チェックに関する情報は、以下を見るのがよさそうです。
型の定義にはスタブファイルも使うようですね。
提供形態はいくつかあります。
自分が以前インストールしたのは、Language ServerとしてのPyrightをEmacsで使う方法ということになります。
またCLIとしてはnpmでインストールしています。
Pythonプロジェクトに導入することが多いと思いますので、今回はPyPiパッケージとして使ってみましょう。
またMypyとの違いも気になるところですね。こちらに書かれています。
Differences Between Pyright and Mypy
- MypyはPEP 484のリファレンス実装であり、多くの型チェックを規定しているものの動作が意図的に未定義になっている箇所もある
- PyrightはPythonの型システム仕様に準拠したもの
各型チェッカーの実行結果はこちら。
Python Type System Conformance Test Results
Mypyとの動作や推論の違いについても書かれていますが、それ以外の違いとしては以下も大きいでしょう。
- Pyrightの方がMypyよりも高速なことが多い
- PyrightはLanguage Serverの基盤になっている
- Mypyのようなプラグイン機構はPyrightにはない
環境
今回の環境はこちら。
$ python3 --version Python 3.12.3 $ uv --version uv 0.9.5
準備
まずはuvプロジェクトを作成して準備します。
$ uv init --vcs none pyright-getting-started $ cd pyright-getting-started $ rm main.py
プロジェクトにはRuffくらいを追加しておきます。
$ uv add --dev ruff
Pyrightを使ってみる
それではPyrightを使ってみましょう。
開発用パッケージとしてPyrightを追加。
$ uv add --dev pyright
pyproject.tomlはこのようになりました。
pyproject.toml
[project]
name = "pyright-getting-started"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
[dependency-groups]
dev = [
"pyright>=1.1.407",
"ruff>=0.14.2",
]
インストールされた依存関係。
$ uv pip list Package Version ----------------- ------- nodeenv 1.9.1 pyright 1.1.407 ruff 0.14.2 typing-extensions 4.15.0
バージョンとヘルプ。
$ uv run pyright --version pyright 1.1.407 $ uv run pyright --help Usage: pyright [options] files... Options: --createstub <IMPORT> Create type stub file(s) for import --dependencies Emit import dependency information -h,--help Show this help message --ignoreexternal Ignore external imports for --verifytypes --level <LEVEL> Minimum diagnostic level (error or warning) --outputjson Output results in JSON format -p,--project <FILE OR DIRECTORY> Use the configuration file at this location --pythonplatform <PLATFORM> Analyze for a specific platform (Darwin, Linux, Windows) --pythonpath <FILE> Path to the Python interpreter --pythonversion <VERSION> Analyze for a specific version (3.3, 3.4, etc.) --skipunannotated Skip analysis of functions with no type annotations --stats Print detailed performance stats -t,--typeshedpath <DIRECTORY> Use typeshed type stubs at this location --threads <optional COUNT> Use separate threads to parallelize type checking -v,--venvpath <DIRECTORY> Directory that contains virtual environments --verbose Emit verbose diagnostics --verifytypes <PACKAGE> Verify type completeness of a py.typed package --version Print Pyright version and exit --warnings Use exit code of 1 if warnings are reported -w,--watch Continue to run and watch for changes - Read files from stdin
では使ってみましょう。試しに関数に定義した引数の型と、実際の呼び出し時に指定している型が異なるスクリプトを用意。
bad_type.py
def add(a: int, b: int) -> int: return a + b print(add("foo", "bar"))
実行。
$ uv run pyright /path/to/pyright-getting-started/bad_type.py /path/to/pyright-getting-started/bad_type.py:4:11 - error: Argument of type "Literal['foo']" cannot be assigned to parameter "a" of type "int" in function "add" "Literal['foo']" is not assignable to "int" (reportArgumentType) /path/to/pyright-getting-started/bad_type.py:4:18 - error: Argument of type "Literal['bar']" cannot be assigned to parameter "b" of type "int" in function "add" "Literal['bar']" is not assignable to "int" (reportArgumentType) 2 errors, 0 warnings, 0 informations
intの引数には割り当てられないと怒られましたね。
では、型宣言のない関数を追加してみます。
bad_type.py
def add(a: int, b: int) -> int: return a + b print(add("foo", "bar")) def message(s): print(s)
実行すると、特にエラーが増えませんでした。
$ uv run pyright /path/to/pyright-getting-started/bad_type.py /path/to/pyright-getting-started/bad_type.py:4:11 - error: Argument of type "Literal['foo']" cannot be assigned to parameter "a" of type "int" in function "add" "Literal['foo']" is not assignable to "int" (reportArgumentType) /path/to/pyright-getting-started/bad_type.py:4:18 - error: Argument of type "Literal['bar']" cannot be assigned to parameter "b" of type "int" in function "add" "Literal['bar']" is not assignable to "int" (reportArgumentType) 2 errors, 0 warnings, 0 informations
デフォルトのチェックルールには含まれていないようですね。ここで設定を確認してみます。
PyrightではtypeCheckingModeでルールセットを指定できるようです。off、basic、standard、strictの4つから指定でき、デフォルトでは
standardになっているようです。
Pyright Configuration / Type Check Diagnostics Settings
設定はpyrightconfig.jsonまたはpyproject.tomlに記述します。
今回はpyproject.tomlに記述しましょう。
pyproject.toml
[project]
name = "pyright-getting-started"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
[dependency-groups]
dev = [
"pyright>=1.1.407",
"ruff>=0.14.2",
]
[tool.pyright]
typeCheckingMode = "strict"
strictにしました。
再実行。
$ uv run pyright /path/to/pyright-getting-started/bad_type.py /path/to/pyright-getting-started/bad_type.py:4:11 - error: Argument of type "Literal['foo']" cannot be assigned to parameter "a" of type "int" in function "add" "Literal['foo']" is not assignable to "int" (reportArgumentType) /path/to/pyright-getting-started/bad_type.py:4:18 - error: Argument of type "Literal['bar']" cannot be assigned to parameter "b" of type "int" in function "add" "Literal['bar']" is not assignable to "int" (reportArgumentType) /path/to/pyright-getting-started/bad_type.py:6:13 - error: Type of parameter "s" is unknown (reportUnknownParameterType) /path/to/pyright-getting-started/bad_type.py:6:13 - error: Type annotation is missing for parameter "s" (reportMissingParameterType) /path/to/pyright-getting-started/bad_type.py:7:11 - error: Argument type is unknown Argument corresponds to parameter "values" in function "print" (reportUnknownArgumentType) 5 errors, 0 warnings, 0 informations
今度はエラーになりました。
typeCheckingModeで指定できる値と、指定に応じてどのルールがどのような設定になるのかは、以下で確認できるようです。
Pyright Configuration / Diagnostic Settings Defaults
strictにしたからといって、すべてのルールが有効になるわけではなさそうですね。
typeCheckingModeを設定してから、個別のルールを必要に応じてオーバーライドするのがよさそうです。
Pyright Configuration / Type Check Diagnostics Settings / Type Check Rule Overrides
strictで有効になっていないものとPythonのバージョン指定を含めて、こんな感じではないでしょうか。
[tool.pyright] pythonVersion = "3.12" typeCheckingMode = "strict" deprecateTypingAliases = true reportImplicitOverride = "erorr" reportImplicitStringConcatenation = "error" reportImportCycles = "error" reportMissingSuperCall = "error" reportPropertyTypeMismatch = "error" reportUnnecessaryTypeIgnoreComment = "error" reportUnreachable = "error"
これで少し試してみましょう。
おわりに
Pythonの静的型チェッカー、Pyrightを試してみました。
Mypyよりも高速ということなのですが、今回はその差を実感するような確認はしていません。
実行形態や設定方法などもなんとなくわかったので、ちょっと使っていってみましょう。