世界を動かす技術を、日本語で。

PyreflyとTy: Pythonの2つの新しいRustベースの型チェッカーを比較する

概要

  • Pyreflyty は新しいRust製Python型チェッカー
  • どちらも従来の mypypyright より高速
  • 速度・設計思想・インクリメンタル処理・型推論に違い
  • それぞれMetaとAstralが開発、オープンソース指向
  • まだアルファ版、今後の進化に期待

Pyreflyとty:新世代Rust製Python型チェッカー比較

  • 2025年PyCon Typing Summitで Pyrefly (Meta製)と ty (Astral製)が公式初披露
  • どちらも Rust で開発され、従来のPython型チェッカー( mypypyright 等)より高速
  • Pyrefly はMetaの旧型チェッカーPyre(OCaml製)の後継
    • Pyreより 35倍高速、mypy/pyrightより 14倍高速、1.8M行/秒対応
    • オープンソースコミュニティと積極的に連携
  • ty はAstral(uvやruff開発元)が開発
    • 旧名Red-Knot、現在はtyとして展開
    • 公開は控えめだが、 1~2桁高速 を目標
  • 両者とも Ruff をASTパーサとして採用
  • CLI型チェックとLSP/IDE統合に対応
  • 共通点は高速なRust実装だが、設計思想や細部で違い

速度比較ベンチマーク

  • PyTorch リポジトリ全体・torchサブディレクトリでテスト
    • tyがpyreflyの 2~3倍高速、mypy/pyrightの 10~20倍高速
    • 検出ファイル数に差(pyrefly:約8600、ty:約6500)
  • Django 5.2.1 でも同様の傾向
    • ty: 0.6秒(2900ファイル)、pyrefly: 0.9秒(3200ファイル)、pyright: 16秒
  • mypyリポジトリ (mypy/mypyc)でも
    • ty: 74ms、pyrefly: 136ms、mypy: 3.5秒、pyright: 2.8秒

設計思想・目標

  • Pyrefly :型推論を最大限活用し、明示的型なしでも型保証を目指す
  • ty :「グラジュアル保証」重視
    • 型アノテーションを外しても型エラーが発生しない設計
    • 例:属性にNoneを代入後、intを代入してもエラーにならない(Unknown型導入)
  • Pyrefly は推論が積極的な分、エラー検出も多い

インクリメンタル処理の違い

  • Pyrefly :独自エンジンで モジュール単位 インクリメンタル型チェック
    • ファイル単位で再パース、Rustの高速性で十分と判断
  • tySalsa (Rust Analyzer同様)で 関数単位 の微細なインクリメンタル型チェック
    • 依存関数のみ再パース、より細かい最適化

機能・型推論の違い

  • どちらも アルファ版、機能は発展途上
  • Pyrefly は暗黙的型推論に強み
    • 明示的型指定なしでも複雑な型推論が可能
  • ty は「Unknown」型で柔軟性重視
    • Anyとの違いを明確化、型安全性と利便性のバランス

    • 例: my_list = [1, "b", None] の場合

      • pyrefly: int | str | Noneと推論し、val * 2でエラー
      • ty: Unknown型で柔軟に対応、エラーなし
      • mypy, pyright: objectやUnknown型で曖昧な挙動

まとめと今後

  • Pyrefly は積極的な型推論・高速性・コミュニティ連携重視
  • ty はグラジュアル保証・極細粒度インクリメンタル・柔軟性重視
  • どちらも従来型チェッカーより 圧倒的な速度新しい設計思想 を提示
  • まだ アルファ版 のため、今後の機能追加・安定化に期待

Hackerたちの意見

tyは、別のマントラに従っている: 漸進的保証。基本的なアイデアは、型付けがしっかりしたプログラムでは、型アノテーションを削除しても型エラーが発生しないということ。つまり、動作しているコードに新しい型を追加して型エラーを解決する必要はないってこと。Tyが提供する漸進的保証は面白いね。それを試してみようかと思ってる。Pythonみたいな既存の動的コードベースを持つ言語では、漸進的型付けをするのにちょうどいい方法に思える。

グリーンフィールドでない限り、漸進的型付けが唯一の方法だよね。いくつかのレガシーPythonコードベースにmypyを使って型ヒントを組み込んだけど、実際に賢い方法はモジュールごとに「オプトイン」することだけだと思う。もしpyreflyがそれをサポートしないなら、使い道はかなり限られると思う。もしかしたら、llmコード生成の方向を狙ってるのかもしれないけど。llmがPythonスクリプトを生成するための、すごく速くて厳密な型チェッカーが役立つのは見えるな。

これは、Typescriptの初期の頃を思い出させるね。既存の大規模プロジェクトにスムーズに導入することに焦点を当てていたから。より制約のある要件(例えばnoImplicitAny)は、一つずつオンにできて、最終的にstrictスイッチを切り替えてすべてのチェックにオプトインすることができた。

漸進的型付けって、コードベースのどこにでもある暗黙の「any」(不明な型)がエラーでも警告でもないってことなんだ。完全に型付けされてると思ってた重要なコードでもね。間違って型バグを導入して、文法や推論の制限で型チェッカーが予期せずにおかしくなって、「このファイルに問題はありません!」って自信満々に言ってくるのが理解できるけど、私がmypyを試したときの最終的な問題は、型からの保護を実際に保証する方法がなかったこと。 「このファイルには漸進性はない、完全に型付けされてる!」って主張する方法が重要だけど、漸進的型付けは移行だけじゃなくて、動的言語でできるクレイジーなことや、静的型付けを重視しない人を怖がらせる誤検知の恐怖とも関係してる。もしかしたら「ソフト」型付けって呼ぶ方が分かりやすいかも。今のところ、漸進的型付けはアンチパターンだと思う。

Pythonを書くためにお金をもらっているけど、Rustプログラマーだと思っているし、徐々に保証される方が理にかなっていると思う。

これ、私にはすごく嫌だな。Pythonに型アノテーションを追加する半分の目的は、エラーが起きやすい動的型付けを抑えることなんだよね。Pythonが許可しているからって、バカなことをした時に気づかないのは嫌だ。だから、動かすコードを大事にする人のために、暗黙のAnyを使わないモードとか「厳格モード」を追加してほしいな。

どこかの型チェッカーが本格的なノートブック統合を始めてくれるといいな。ライブコーディングのための統合で、ノートブックを静的にチェックするバッチスクリプトだけじゃなくて。1〜60分のセルを実行する前に型でエラーを見つけられるのは大きな勝利だよ。

VSCodeでJupyterノートブック使ってる?普通のPythonファイルと同じpylanceを使うから、使い捨てコードを書くときにはちょっとイライラするんだよね。

他のレスに同意だね。VSCodeのノートブックを使って静的型チェックをするべきだよ。言語サーバーは、ノートブックの統合や「ライブコーディング」を含めて、まさに君が求めていることをやってくれるんだ。

my_list = [1, 2, 3] > pyrefly、mypy、pyrightは、my_list.append("foo")が型エラーだと仮定してるけど、技術的には許可されてる(Pythonのコレクションは複数の型のオブジェクトを持てるから!) > もしこれが意図された動作なら、tyはmy_listに追加の明示的な型付けを要求せずに暗黙的にこれを許可する唯一のチェッカーだね。EDIT: 私のコメントがこんなに厳しくなるつもりはなかったんだけど、実はtyが成功することを応援してるよ :) ORIGINAL: ここでのtyの動作には強く反対だよ。プロダクションコードではほぼ常に単一型のリストがあって、型チェッカーがこれを前提にすることが重要なんだ。特にリストが同じ型のリテラルアイテムを既に持っている場合はね。Pythonがこれを許可していることは全く関係ない。私にとって、型チェッカーがlist[int | str]を暗黙的に許可するのは、初心者レベルのコードを最適化してるように思える。

初心者向けのコードを最適化してるとは思わないな。むしろ、レガシーコードの最適化をしてる感じ。大規模な未型付けのコードベースに型チェッカーを導入するのは大変だけど、既存のコードのほとんどが受け入れられれば、負担は少なくなるよね。

そして、型チェッカーがこれを前提とすることは重要です。 でも、なんでそれが重要なの?もし list[int] が必須なら、そこでは型エラーが出るべきだと思うけど。

わからないな。Pythonの型チェックはオプションだから、プログラマーが気にしない限り、型チェッカーも気にしなくていいと思う。もっと面白いケースは、my_list.append(2.45) や my_list.append(Decimal("2.0")) だね。これらは「数値」だけど「整数」じゃない。現実の世界では、CSVデータの行は型チェックされないし、スプレッドシート業界も型付きCSVデータを採用するようにはなってないよ。

それは、そのリストの後に何が起こるかによるね。整数特有の操作が適用されるのか、それともただ表示されるだけなのか?もしそれが、整数を文字列にキャストできるstr属性を持つオブジェクトに渡されたらどうなる?

私はここでのtyの振る舞いに強く反対です。[tyの開発者です] tyはまだ完成していないことに注意してください!この特定の例では、リストリテラルの型を推測するためにtyが特に賢いことをしていないので、つまずいています。要素が何であれ、単に list[Unknown] としてプレースホルダーを推測するだけです。Unknown は段階的な型(Any と同じように)なので、append の呼び出しは成功します。なぜなら、すべての型が Unknown に代入可能だからです。リストのより正確な型を推測する計画はありますが、周囲の文脈でリストに対して何をしているかを考慮するために「双方向」の型付けが必要になるので、予想以上に複雑になるでしょう。それに関するトラッキングの問題はこちらにあります: https://github.com/astral-sh/ty/issues/168

pyreflyの挙動の問題は、大きなコードベースがPythonの型チェックを使っていない場合、このツールを段階的に導入できないことだよね。すべての問題を修正しないといけないから、移行には広範なサポートが必要だよ。Metaの内部ツールならそれでいいけど、エンジニアリングリーダーシップの優先事項にこの種の変更が含まれていない組織でツールを徐々に導入する場合は、もっと受け入れやすい方がいいと思う。だから、tyのやり方が好きなんだ。自分のコードでは、こういう型の混合があったら警告してほしいけどね。

tyの挙動には強く賛成だな。動いているPythonコードが型エラーを出すべきじゃないと思う。ユーザーが型アノテーションを追加して、より静的なサブセットを選択しない限りね。

Pythonがこれを許可しているという事実は全く関係ないよ。俺にとって、typecheckerが暗黙的にlist[int | str]を許可するのは、初心者向けのコードを最適化してるように見える。そうだね、君の意見に基づいてツールを作るより、Pythonで許可されていることに基づこうよ。

list[int | str]は普通は間違いかもしれないけど、my_list = [BarWidget(...), FooWidget(...)]はどう? my_list.append(BazWidget(...)) my_list.append(7) そこで型チェッカーが、ほぼ確実にlist[Widget]として意図されている型ヒントを推測できて、最初のappendを許可して、二つ目をフラグできたらいいと思わない?

Pyreflyの強い型推論の方が好きかも。ダイナミズムが多いプロジェクトでは面倒なこともあるけど、個人的にはそのトレードオフを受け入れるよ。

[tyの開発者です] tyが注目を集め始めていることに満足していますが、tyとpyreflyの両方がまだ未完成であることを強調することが重要です!(OPもこれに言及していますが、ここでも再度強調する価値があります。)まだ実装されていない機能に当たる例が確実に出てきています。ですので、私たちのやっていることが馬鹿げていると思うようなことに出くわしたら、まだ手が回っていないだけかもしれないことを理解してください。Pythonは大きな言語ですから!

@TODOとして明示された型を見たときは笑っちゃったけど、考えてみると、実は結構いいアイデアだね!

ちょっと別の質問だけど、Rust開発のその側面に詳しい君に聞きたいことがあるんだ。「Rust用のスクリプト言語」の話が何度か出てきたけど、Rustの構文にうまくフィットして、Rustと一緒にコンパイルできて、Rustの型をネイティブにインポートできて、でもコンパイルや実行、ホットリロードが早い言語って知ってる?君のネットワークでそのことに取り組んでいる人がいるかどうか教えてほしい。そして、構文の部分を除けば、Pythonがその隙間を埋める可能性はあると思う?

あのMarkdownスタイルのテスト、めっちゃ好き!テストがドキュメントとしても機能する素晴らしいアイデアだと思う。どうやってこの解決策を思いついたのか教えてくれる?Rustのドキュメントのコード例からインスパイアされたの?

この2つにはすごく興味があるよ。TypeScriptの世界から来たから、型推論の有無や交差型、型の絞り込みなど、いろんな方向性に興味津々。Python開発者としては、4つ以上の型チェッカーがあって、それぞれ動きが違うのに疲れ果ててる。ほんとにPythonらしいよね。でも、これらのプロジェクトにはすごく興味を持って追ってる。結局のところ、良い型チェッカーはコードをもっと早く、信頼性高く書けるようにしてくれるはずなんだけど、今のPythonの型チェックの状態ではそうなってない気がする。プロジェクト頑張ってね!

私は、Tyの振る舞いよりもPyreflyの振る舞いに近い型チェッカーをいくつか作ったことがあります(異なる言語で)。型安全な大規模なコードベースを持っているなら、Pyreflyのアプローチは、初期の負担がかなり大きいとしても、全体的に型注釈を書く量がずっと少なくて済むんですよ。Tyは実質的にnoImplicitAnyがfalseに設定されています。

いいね、今はbasedpyrightを使ってるよ。IDEでの型チェッカーとしても、GitHub Actionsでも使ってる。良い感じで、主に自分がやりたいことをやってくれる。mypyはあまり好きじゃないな、時々シンプルな型付けでも苦労するから。

Pythonプログラミングには詳しくないけど、外部者としての意見を言わせてもらうね。これらのツールを使いたい人には、以下のリンクを読むことをおすすめするよ。https://www.reddit.com/r/Python/comments/10zdidm/why_type_hi... あの投稿は軽く受け止めるべきだと思うけど、目指しているのは、たとえ最高の型ツールを使っても、良いプラクティスを確立しない限り問題が出るってことを理解することだと思う。例えば、Djangoは大きなコードベースで、そのコードを見ると、どのPythonの機能が使われているか、どう使われているかが一貫しているのがわかる。このプロジェクトは、厳しい型チェックテストを問題なく通過しているよ。同様に、Metaも非常に大きなコードベースを持っているはず(そうでなければ型チェッカーを開発する意味がないからね)。プログラマーが好きなようにコードを書かせるわけにはいかないって気づいたんだろうね。だから、彼らの型チェッカーは厳しいものになっていると思う。Pythonは、私の知る限り、多くの機能を持っていて、非常に緩やかなランタイムを持っているけど、コードを管理しやすくするためには、限られたサブセットだけを使うべきだと思う。残念ながら、そのサブセットは誰に聞くか、何を目指すかによって違うだろうね。(面白いことに、あのRedditの投稿は、Rustの人たちがLinuxカーネルの人たちに自分たちのやり方を受け入れてもらうのに苦労していることを思い出させた。Cはもっとシンプルで気楽な型システムを持っているけど、Rustはもっと厳格だから、そのCの人たちには受け入れられにくいんだよね。)

今のところ、Pythonプログラムの型チェックにかける労力は、Pythonからちゃんとした型システムを持つ言語に移行する方が良いと思ってる。Interopを使えば、Pythonが必要な部分や人はそのままPythonでいられるしね。もちろん、いつも可能ってわけじゃないけど、Pythonを無理やり使おうとすると時間がかかりすぎるよ。

Pythonは、私の知る限り、多くの機能を持っていて、非常に寛容なランタイムだけど、(C++と似て)コードを管理しやすくするためには、限られたサブセットだけを使うべきだと思う。残念ながら、そのサブセットは誰に聞くか、何を目指すかによって違うんだよね。私が自分のコードベースで使いたくないPythonのサブセットについて話すと、メタクラス、デスクリプタ、__call__を使った呼び出し可能オブジェクト、object.new(cls)、名前が名前マングリングのルールを引き起こすこと、self.__dict__などがある。私の意見では、これらの機能は魔法がかかりすぎていて、コードの理解を妨げると思う。

その投稿のトップコメントが、全てのナンセンスをすぐにしっかり否定してるね:> もしそんな超汎用的な関数があって、型ヒントが強制されているなら、Anyを使って気にしないでおけばいい。バカな例だけど、ライブラリのslow_add関数の文脈でも、作者は最初に人々が非数値の値を渡すとは思ってなかったかもしれないから、次のバージョンアップでハードコーディングされたtime.sleep(0.1)の代わりにtime.sleep(a / b)にしたら、今度は文字列やタプルを渡したユーザーがクラッシュすることになっちゃった!もしその関数が数値の値だけに使うことを意図していると宣言できる方法があれば、予期しない使い方をしたユーザーのために後方互換性を持たせる必要がなくなるのに。私の意見では、非対話的に動作するPythonに対しては、型チェックは当たり前だよ。型チェックを追加しない選択をするのは、明らかに間違いを犯してる。

あなたがその言語を使っていないのに、どうやって意見を形成するのか少し教えてもらえる?特に型に関する議論が、使ったこともない言語に対して人々が怒りをぶつける様子は面白いと思うし、あなたの投稿のように作り話の例を持ち出すのもね。> そこでは、最高の型ツールがあっても、良いプラクティスを確立しない限り問題が起こることを理解するのが目的だと思う。 - どうしてPython開発者がPythonについて理解していないと思うの?彼らはあなたとは違って、その言語を積極的に使ってるのに。

長い投稿を要約するとね:> ライブラリの引数に型ヒントを追加すると、Hyrumの法則に悩まされることになるし、ユーザーの全体像を正確に型付けする準備ができてないってことだよね。それは理解できる。でも、彼らは破壊的な変更をしていて、それはただの破壊的変更の痛みなんだ。もし彼らがこうしていたらほぼ同じことになる:def slow_add(a, b): throw TypeError if !isinstance(a, int) ... でも、そのコードを見たら「まあ、これは破壊的変更だよね、当然文句が出るよ」と言うだろう。ここでの唯一の本当の違いは、これは開発者にとっての破壊的変更であって、実行時に破壊的ではないってこと。Pythonは実行時に型ヒントを強制しないからね。既存のコードは動くけど、コードを見ている既存のツールは失敗する。これは簡単な回避策を提供する(無視するだけでいい)けど、開発者にとっては同じコードが同じ方法で変更される必要があるから、同じように中断されるんだ。--- 対照的に!ライブラリは非常に頻繁に戻り値に型を追加できて、それがユーザーにとってすぐに役立つんだよね。君は出力を既に出力している値だけに制限しているわけで、基本的に定義上、間違ったコードだけが失敗することになる。

Astralのツールは素晴らしくて、Pythonの世界に新しいエネルギーをもたらしてるけど、Astralプロジェクトの長期的な目標は何なんだろう?Pythonにネイティブに統合するつもりなのかな?それとも5年後には消えて、メンテナンスされないツールだけが残るのかな?サブスクリプションでみんなを裏切るとか?

数十年にわたって、大手テック企業はPythonエコシステムのツールにあまり貢献してこなかったよね。FacebookのPyreはあるけど、それくらいしかない。パッケージや依存関係の管理、リント、フォーマットに関しては何もないから、Astralのような人たちがそのギャップを埋めるために立ち上がった。なんで型チェックだけが例外なの?GoogleやFacebook、Astralがそれぞれ自分たちのmypyの代替を作ってる中で、急にこの分野が賑わってるのが気になる。

多分、たくさんのAIがPythonコードを生成していて、その出力を素早くサニタイズ/バリデートするためにタイプチェッカーが必要なんだろうね。ダイナミック言語は人間が理解するのも大変なのに、AIエージェントはもっと苦労してると思うよ。

Instagramはエラーを修正できるリンターを作ったんだけど、これはflake8やpylintよりも改善されてるよ: https://github.com/Instagram/Fixit でも、Ruffはそれをさらに上回る改善なんだ。