mcpField
P
pyrefly-type-coverage

pyrefly-type-coverage

github.com/pytorch開発105.2k3.2k 回インストール今日更新
リポジトリ

pyrefly-type-coverage について

pyrefly-type-coverageは、PyTorchのPythonファイルでPyreflyの型カバレッジを高めるスキルです。設定、型注釈、Lint、テストの手順と、後方互換性のあるシグネチャを保つ方法を示します。

主な機能

  1. 01Pyrefly設定のガイド
  2. 02ファイル単位の抑制の削除
  3. 03型注釈の規約と型の絞り込み
  4. 04Lintとテストの手順
  5. 05後方互換性のあるシグネチャへの対応

活用シーン

  1. 01Pythonモジュールに型注釈を追加
  2. 02Pyreflyの暗黙のAnyや注釈不足のエラーを解消
  3. 03互換シグネチャを変えずに型カバレッジを改善

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/pytorch/pytorch --skill pyrefly-type-coverage

インストール後、Claude Code で/pyrefly-type-coverageを入力して読み込まれたか確認します。

詳細

Pyrefly 型カバレッジスキル

前提条件

  • ファイルは pyrefly.toml があるプロジェクト内に配置する必要があります。
  • pyrefly、lintrunner、およびプロジェクトのテストランナーが PATH 上にある必要があります。いずれかが見つからない場合は、conda 環境を有効にする必要があるか確認してください。インストールしたり、別のものに置き換えたりしないでください(リポジトリの CLAUDE.md に従ってください)。
ステップ 1:ファイルレベルの型チェック抑制を削除する

ファイルの先頭から、以下のいずれかを削除します(pyrefly は mypy との互換性のために # mypy: ignore-errors も適用するため、これも削除する必要があります)。

# pyre-ignore-all-errors
# pyre-ignore-all-errors[16,21,53,56]
# @lint-ignore-every PYRELINT
# mypy: ignore-errors
ステップ 2:pyrefly.toml にサブ設定のエントリーを追加する
[[sub-config]]
matches = "path/to/directory/**"
[sub-config.errors]
implicit-import = false
implicit-any = true
bad-param-name-override = false
unannotated-return = true
unannotated-parameter = true

重要:[sub-config.errors] でエラーキーを設定すると、親設定に対してそのキーのみが上書きされます。ただし、unannotated-return/unannotated-parameter/implicit-any を有効にすると、以前はファイル全体で隠されていたエラーが再び表示されます。無関係なエラー(例:bad-param-name-override)が大量に出力される場合は、そのエラーを抑制するために、親設定のそのキーの設定をサブ設定にも反映してください。

ステップ 3:pyrefly を実行する
pyrefly check <FILENAME>

目標: アノテーションを追加して、unannotated-return、unannotated-parameter、implicit-any のエラーをすべて解決します。ステップ 4 の手順を参照してください。この 3 つの対象カテゴリは必ず解決できます。# pyrefly: ignore で抑制してはいけません。唯一の例外は @compatibility(is_backward_compatible=True) です(ステップ 4)。

その他のカテゴリ(bad-argument-type、missing-attribute、…)は実際の型のバグです。pyrefly が報告した場所に応じて対応してください。

  • 別のファイルで報告された場合(パス != 対象ファイル):そのままにします。対象範囲を広げないでください。エラーが対象ファイルの作業を妨げている場合は、報告箇所で # pyrefly: ignore[<category>] # TODO を使って抑制します。
  • 対象ファイルで報告されたが、メッセージが別の場所で定義されたシンボルを指している場合 (例:インポートした関数のアノテーションが誤っているために bad-return が発生している場合):同じ TODO コメントを使ってローカルで抑制します。上流の不足を覆い隠す cast() を作らないでください。
  • 対象ファイルで報告され、原因もそのファイル内にある場合:修正します。

# pyrefly: ignore[...] は、最後の手段として、対象外のカテゴリにのみ使用してください。

ステップ 4:アノテーションを追加する

適切な型が関数本体から明らかでない場合は、呼び出し箇所を調べます。

アノテーションの規則
  • PEP 604/PEP 585 の構文(int | None、list[str])を使用してください。Python >= 3.10 を前提とします。
  • ABC には typing より collections.abc を優先してください(Callable、Sequence、Generator など)。
  • 汎用ヘルパーでは、プロジェクトの最小 Python バージョンで利用できる場合は typing からインポートし、より新しい機能が必要な場合に限り typing_extensions からインポートしてください(例:3.11/3.12 未満をサポートする場合の Self と override、または TypeVar/ParamSpec の PEP 696 default=)。typing_extensions から一律にインポートしないでください。
  • Callable には必ず型引数を指定してください(引数なしの Callable は使用しないでください)。Callable[..., object] を優先してください。呼び出し元が動的な戻り値を実際に使用する場合に限り Callable[..., Any] を使用します。結果をそのまま渡すだけの場合(または呼び出し可能オブジェクトを実際には呼び出さない場合)は、object のほうが厳密で、同じく正しい型です。(シグネチャを保持するラッパーの場合については、以下の ParamSpec を参照してください。)
  • 導入するモジュール内グローバルには、すべて先頭にアンダースコアを付けてください。TypeVar/ParamSpec(文字列引数と一致させます:_T = TypeVar("_T")、_P = ParamSpec("_P")、_R = TypeVar("_R"))、TypeAlias、ヘルパー定数、センチネルも同様です。これは非公開名に対する torch の一般的な規約です(ツリー内では P より _P が約 6 倍多く使われています)。例外(アンダースコアを付けないもの):他のモジュールからインポートされる名前、__all__ に列挙されている名前、実行時トークンとして使われる名前(注釈文字列のディスパッチマーカーなど)。追加する名前にのみ適用してください。既存のグローバル名は変更しないでください。これは、このスキルの範囲外である別のリファクタリングです。
  • 真偽値を返す述語(is_*/has_* という名前で、多くの場合 object など広い型を受け取り、bool を返すもの)には、通常 TypeGuard[X](または否定側の分岐も型を絞り込む TypeIs[X])が適しています。TypeGuard は typing にあります(>= 3.10 のため、そこからインポートしてください)。TypeIs が typing に追加されたのは 3.13 からです。そのため、3.10 との互換性を保つには typing_extensions(>= 4.10)からインポートしてください。klass: type[_T] を受け取る issubclass スタイルのヘルパーは、TypeGuard[type[_T]] を返すようにしてください。issubclass() の周囲で try/except TypeError を使うより、明示的な isinstance(x, type) ガードを優先してください。こちらのほうが明確で、型チェッカーによる型の絞り込みも可能です。
  • 戻り値の型が引数から導かれる場合(そのまま返す関数/恒等関数、「これらの引数のいずれかを返す」ヘルパー、デコレーター、型をキーとするレジストリー)は、object/Any に広げるのではなく TypeVar を使用してください(または、シグネチャが引き継がれる呼び出し可能引数の場合は、ParamSpec/TypeVar とともに Callable[_P, _R] を使用します)。「出力の型 = 何らかの入力の型」は、まさに TypeVar が表すものです。object を入力し object を出力する型指定では、その情報が失われます。例外:値を変換して出力の型が入力の型と異なる場合(例:配列を整数に変換する場合)は、単一の TypeVar は不適切です。実際のドメイン型を指定してください。
  • __init__ で値を代入するクラス属性には、pyrefly が認識できるようクラスレベルの注釈を付けてください。
  • if TYPE_CHECKING: を使ってインポートの循環を解消してください。注釈にのみ使うインポートはガード内に置き、実行時のインポートを遅延させるために from __future__ import annotations(または文字列による前方参照)を使用してください。
    from __future__ import annotations
    from typing import TYPE_CHECKING
    if TYPE_CHECKING:
        from torch.fx import GraphModule
    def transform(gm: GraphModule) -> GraphModule: ...
    
  • 対象となる 3 つのカテゴリは、決して抑制しないでください。 unannotated-return、unannotated-parameter、implicit-any は、注釈を追加すれば常に解決できます。# pyrefly: ignore[<one of those>] は許容される対応ではありません。唯一の例外は、以下の後方互換性に関する特例です。
  • 諦めずに、型を広げてください。 適切な型を推定するのが難しい場合は、無視するのではなく、次の段階を順に検討してください。
    1. 呼び出し箇所と戻り値の経路から確認できる、最も具体的な型。
    2. 共用体(X | Y)、Sequence[X] のような抽象型、または本当に汎用的な関数(恒等関数やそのまま渡す関数、コンテナー用ヘルパー)のための上限付き TypeVar。
    3. object — 型チェックを通過する範囲で、最も厳密な代替型です。使用前に呼び出し元で型を絞り込む必要があります(例:def serialize(value: object) -> str:)。見た目は Any に似ていますが、より厳密です。isinstance を使わずに value.foo() を呼び出すと、pyrefly はエラーを報告します。
    4. Any — 最後の段階です。対象カテゴリに対する # pyrefly: ignore よりは常に優先されますが、1〜3 の段階で解決できない場合に限り使用してください。前の各段階が適さない理由(例:「共用体の型が 8 種類を超える」、「観察できる共通の上限がない」、「呼び出し元で実際に型を絞り込むことがない」)を説明できるようにしてください。
  • 戻り値に object/Any を使う場合は、特に慎重に判断してください。関数は通常、呼び出し元よりも生成する値について多くの情報を持っています。広い戻り値型が適切なのは、本当に境界となる場合(入力を変更せずに返す場合、または値をハンドラー/呼び出し元が定義する場合)に限られます。関数本体が既知の形状を構築する場合は、その型を指定してください(ドメイン型のエイリアスや共用体型は object より適切です)。
  • 引数に Any が必要だと判断する前に、少なくとも 3 つの呼び出し箇所を確認してください。最初に見たコードが動的に見えるという理由だけで判断しないでください。
  • 範囲を絞った # pyrefly: ignore[...](対象外カテゴリに対するもの)は、pyrefly が特定のローカルエラーについて実際に誤った報告をする場合に限って使用してください(動的なメタプログラミング、サードパーティー製スタブの不足など)。
    # pyrefly: ignore[attr-defined]
    result = getattr(obj, dynamic_name)()
    
  • インラインの # pyrefly: ignore[...] を付けると行の長さ制限を超える場合は、# fmt: skip を使って同じ行に置くのではなく、エラーがある行の直前に置いてください。pyrefly は直前行の ignore を認識します。(例外:以下の後方互換性に関する特例では、def 行に置く必要があります。)
下位互換性(抑制を決して行わないルールの唯一の例外)

重要:@compatibility(is_backward_compatible=True) でデコレートされた関数のシグネチャは変更してはいけません。 下位互換性テスト(test_function_back_compat)では、文字列化した inspect.signature をゴールデンファイルと比較します。アノテーションを追加するだけでも(-> None であっても)その文字列が変わり、テストが失敗します。 代わりに pyrefly の ignore コメントを使ってください。

@compatibility(is_backward_compatible=True)
def my_function(  # pyrefly: ignore[unannotated-return]
    self,
    arg1,  # can't add type here either
):
    ...

# pyrefly: ignore コメントは def 行(pyrefly がエラーを報告する行)に置いてください。閉じ括弧 ) には置かないでください。

シグネチャを保持するラッパーの ParamSpec(デコレーターや functools.wraps 形式のヘルパー)。Callable[..., Any] ではシグネチャが失われるため、ラップされた関数のシグネチャを呼び出し元まで引き継ぐには Callable[P, R] を使ってください。ラッパーが任意の呼び出し可能オブジェクトを実際に受け付ける場合は、ParamSpec を省略してください。ラッパーが引数を前置または後置する場合は、Concatenate[X, P] と組み合わせてください。

from collections.abc import Callable
from typing import ParamSpec, TypeVar

_P = ParamSpec("_P")
_R = TypeVar("_R")

def log_calls(fn: Callable[_P, _R]) -> Callable[_P, _R]:
    def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
        return fn(*args, **kwargs)
    return wrapper
ステップ 5:繰り返す

pyrefly check を再実行してください。新しいアノテーションによって、関数が実際には互換性のない型を返す箇所で bad-return エラーが表面化することがよくあります。その場合は修正してください。問題がなくなるまで繰り返します。

共有ヘルパーを厳密化すると(たとえば、TypeGuard やより正確な戻り値を追加すると)、呼び出し元に既存の # pyrefly: ignore コメントが不要になることがあります。再確認し、不要になった抑制コメントと古い説明コメントを削除してください。残したままにしないでください。

ステップ 6:Lint

引き継ぐ前に必ず実施してください。アノテーションによって import の順序や行の長さが変わることがよくあります。

lintrunner -a <files...>

lintrunner で自動修正できない項目は、手動で解決してください。

ステップ 7:テスト

失敗した場合の優先順位:テストの成功 > pyrefly のエラーがないこと > アノテーションの厳密さ。新たに追加したアノテーションによってテストが失敗した場合は、ファイルを元に戻す前に、型の厳密さを一段階緩めてください(たとえば、具体的な型を object にする、または後続の isinstance チェックを壊した Any への拡張を削除するなど)。

  1. 下位互換性のチェック。 grep -l '@compatibility(is_backward_compatible=True)' <target> が対象ファイルを返す場合に限り実行してください。ゴールデンファイルの実際の前提条件は、このデコレーターです。より広範な「torch.fx をインポートしている」というヒューリスティックでは、torch/ の半分が対象になってしまいます。

    python -m pytest test/test_fx.py::TestFXAPIBackwardCompatibility -x -v
    
  2. 変更したモジュールの単体テスト。 カバレッジがないと判断する前に、両方の方法で検索してください。

    # torch/foo/bar.py is usually covered by test/test_foo.py or test/test_bar.py
    ls test/ | grep -i <module-name>
    # or by import
    grep -rl "from torch.foo.bar import\|import torch.foo.bar" test/
    

    どちらの方法でも見つからなかった場合は、ユーザーに伝えてください。黙ってスキップしてはいけません。型の変更によって、実際のランタイム回帰が発生することがあります(Optional[X] と X の違い、.append が呼び出される場合の Sequence と list の違いなど)。

メモ

  • from __future__ import annotations がないクラス本体内の前方参照は、引き続き文字列で囲む必要があります。
    class MyClass:
        def __new__(cls) -> "MyClass": ...
    
  • コミット:ユーザーから明示的に依頼されない限り、コミットしないでください(リポジトリの CLAUDE.md に従ってください)。ファイルに問題がなくなったら、差分をレビュー用に提示して終了してください。

コメント