Python向け爆速静的解析ツールtyを使ってみる
tyとは
Astral社が作っているRust製の爆速な型検査器および言語サーバーです。
インストール
pypiで公開されているのでuv add
すればOK。
uv add ty --dev
速度比較
比較方法
雑にHyperfineでベンチマークを取ってみる。
hyperfine 'uv run mypy --strict src' 'uv run ty check src --exit-zero'
なお、リポジトリは拙作のパッケージを使った。ついでに見てもらえると嬉しい。
結果
mypyと比較するとキャッシュなしで約200倍、キャッシュありで約4倍ほど高速になった。
mypyをキャッシュなしの状態からスタート
mypyをキャッシュありの状態からスタート
便利そうな機能
エラーが見やすい
Cargo (Rustのパッケージマネージャー) の体験を感じるエラーの出力がとてもよい。
error: lint:too-many-positional-arguments: Too many positional arguments to bound method `__init__`: expected 0, got 1
--> src/fukinotou/abstraction/dataframe_exportable.py:65:31
|
63 | return pandas.DataFrame()
64 |
65 | df = pandas.DataFrame(self._to_dicts(include_path_as_column))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66 |
67 | return df
|
info: `lint:too-many-positional-arguments` is enabled by default
Found 1 diagnostic
オプションがありがたい
-W, --watch
Watch files for changes and recheck files related to the changed files
cargo watch
が Pythonでもできる日がくるとは...!! 実際この速度ならwatch機能使いたくなりますよね。次のようにすれば実用レベルのスピードで動きます。
uv run ty check src -W
ぱっと見でもこの辺りのオプションが便利そう。
--error-on-warning
Use exit code 1 if there are any warning-level diagnostics
--exit-zero
Always use exit code 0, even when there are error-level diagnostics
--output-format <OUTPUT_FORMAT>
The format to use for printing diagnostic messages
Possible values:
- full: Print diagnostics verbosely, with context and helpful hints
- concise: Print diagnostics concisely, one per line
--python-version <VERSION>
Python version to assume when resolving types
[possible values: 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13]
これからに期待
一方で、Lintのエラーの理由はよくわからなかった。リポジトリに記載がある通り、正式なリリースまでは職場に持ち込むのは待った方がよさそう。
エラーを調査する人の図
fukinotou (main*) » uv run ty check src ~/hobby/fukinotou
error: lint:too-many-positional-arguments: Too many positional arguments to bound method `__init__`: expected 0, got 1
--> src/fukinotou/abstraction/dataframe_exportable.py:65:31
|
63 | return pandas.DataFrame()
64 |
65 | df = pandas.DataFrame(self._to_dicts(include_path_as_column))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66 |
67 | return df
|
info: `lint:too-many-positional-arguments` is enabled by default
Found 1 diagnostic
このエラーが気になったので ty
を開発している ruff
のリポジトリで全文検索を掛けてみた。(repo:astral-sh/ruff too-many-positional-arguments
)
読んでみたところ、「__init__
に渡す引数が多すぎるよ」という内容のようだった。実際、検索結果には次のようなテストがあった。
# error: [too-many-positional-arguments] "Too many positional arguments to function `cast`: expected 2, got 3"
cast(str, b"ar", "foo")
__init__
なので、pandas.DataFrame
かなと思いpandasのリポジトリで引数を確認した。
def __init__(
self,
data=None,
index: Axes | None = None,
columns: Axes | None = None,
dtype: Dtype | None = None,
copy: bool | None = None,
) -> None:
そういわれると気になってきたので df = pandas.DataFrame(data=self._to_dicts(include_path_as_column))
に替えてみたくなるものである。試したところ、エラーが次に変化した。
fukinotou (main*) » uv run ty check src ~/hobby/fukinotou 1 ↵
error: lint:unknown-argument: Argument `data` does not match any known parameter of bound method `__init__`
--> src/fukinotou/abstraction/dataframe_exportable.py:65:31
|
63 | return pandas.DataFrame()
64 |
65 | df = pandas.DataFrame(data=self._to_dicts(include_path_as_column))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66 |
67 | return df
|
info: `lint:unknown-argument` is enabled by default
Found 1 diagnostic
もしかしたら、私が見落としていて、事前のセットアップが必要だった可能性はある。求む有識者。
Discussion