Pythonは動的型付け言語であることが知られています。 静的型チェックツールでは解析が困難なDSLのようなフレームワークを書くのは非常に簡単です。 それにもかかわらず、 プロトコルやリテラルタイプなどのmypyの最新の機能革新、メタクラスの基本的なサポート、記述子のサポートにより、多くの場合正確なタイプを取得できますが、誤検知やその他のマイナス要因を回避することは依然として困難です。 この問題を解決し、フレームワークごとに型システムをカスタマイズする必要を回避するために、 mypyはプラグインシステムをサポートしています 。 プラグインはPythonのモジュールであり、ライブラリまたはフレームワークと対話するクラスと関数のタイプをチェックするときにmypyが呼び出すプラグインフックを提供します。 したがって、返された関数のタイプをより正確に区別することができます。これは、表現が非常に困難であるか、デコレーターの効果を反映するクラスのメソッドを自動的に生成します。 プラグインシステムのアーキテクチャの詳細と機能の完全なリストを参照するには、 ドキュメントをご覧ください。
標準ライブラリの関連プラグイン
Mypyには、基本的な関数とクラス、および
ctypes
、
contextlib
および
dataclasses
を実装するためのデフォルトのプラグインが付属してい
dataclasses
。 また、
attrs
プラグインも含まれています(歴史的にmypy用に書かれた最初のサードパーティプラグインです)。 これらのプラグインにより、 mypyはこれらのライブラリ関数を使用して、タイプをより正確に判別し、タイプのコードを正しくチェックできます。 これを例で示すには、コードスニペットを見てください。
from dataclasses import dataclass from typing import Generic, TypeVar @dataclass class TaggedVector(Generic[T]): data: List[T] tag: str position = TaggedVector([0, 0, 0], 'origin')
上記では、クラスが定義されるときに
get_class_decorator_hook()
が呼び出されます。 これにより、
__init__()
を含む自動生成されたメソッドが関数本体に追加されます。 Mypyは、このようなコンストラクターを使用して、
TaggedVector[int]
を
position
の型として正しく計算します。 例からわかるように、プラグインはジェネリッククラスでも機能します。
もう1つのコードを次に示します。
from contextlib import contextmanager @contextmanager def timer(title: str) -> Iterator[float]: ... with timer(9000) as tm: ...
ここで、
get_function_hook()
は
contextmanager
デコレータの正確な戻り値の型を提供するため、装飾された関数の呼び出しは特定の型に準拠しているかどうかを確認できます。 mypyはエラーを認識できるようになりました。timer
timer()
引数は文字列でなければなりません。
プラグインとスタブの組み合わせ
動的なPython関数を使用することに加えて、フレームワークはしばしば大きなAPIを持つという問題に直面します。 Mypyは、これらのライブラリを使用するコードをチェックするためにライブラリのスタブファイルを必要とします(ライブラリにビルトインアノテーションが含まれていない場合のみ)。これはあまり一般的ではありません。 typehedを使用した大規模なフレームワークのスタブを配布することは一般的ではありません。
- Typeshedのリリースサイクルは比較的低速です( mypyに同梱されています )。
- 不完全なスタブは、誤った呼び出しにつながる可能性があり、回避することは非常に困難です。
- 異なるタイプのバージョンのスタブを混在させないでください。
PEP 561で導入されたスタブパッケージは次のことを行います。
- 開発者は、好きなだけスタブパッケージをリリースできます。
- パッケージの使用を選択していないユーザーには、誤検出は表示されません。
- いくつかの異なるスタブパッケージの任意のバージョンを安全にインストールできます。
さらに、
pip
使用すると、ライブラリのさまざまなスタブと対応するmypyプラグインを1つのディストリビューションに結合できます。 フレームワークまたは対応するmypyプラグインのスタブは簡単に開発して1つのディストリビューションにまとめることができます。プラグインはスタブの欠落または不正確な定義を埋めるため、非常に便利です。
このようなパッケージの最新の例はSQLAlchemyスタブとプラグインで 、バージョン0.1の最初のパブリックリリースがあり、これは少し前にPyPIで公開されました。 このプロジェクトは初期のAlphaバージョンであるという事実にもかかわらず、DropBoxで安全に使用して型チェックを改善できます。 プラグインは、基本的なORM宣言を理解します。
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String Base = declarative_base() class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String)
上記のコードスニペットでは、プラグインは
get_dynamic_class_hook()
を使用して、 mypyにBaseが有効な基本クラスであることを伝えます。 次に、
get_base_class_hook()
が呼び出されてユーザーを定義し、自動生成された属性を追加します。 次に、モデルのインスタンスを作成します。
user = User(id=42, name=42)
get_function_hook()
呼び出されるため、 mypyはエラーを示す可能性があります。ユーザー名の代わりに
integer
値が受信されます。
スタブは
Column
を汎用記述子として定義するため、モデル属性は正しい型を取得します。
id_col = User.id # Inferred type is "Column[int]" name = user.name # Inferred type is "Optional[str]"
より正確な型をスタブに追加するPRを歓迎します(コアモジュールの進捗状況はここで追跡します )。
スタブの作業中に発見したいくつかの落とし穴を以下に示します。
-
__getattr__()
を使用して、スタブが完了していない初期段階で誤検知を回避します(これにより 、モジュール属性が欠落している場合にmypyエラーが防止されます)。 サブモジュールが欠落している場合は、____init__.py
ファイルでこれを使用することもできます。 - 記述子は、多くの場合、カスタム属性アクセスのより正確な型定義に役立ちます(上記で確認した列の例のように)。 ランタイムの実際の実装が、たとえばメタクラスなどのより複雑なメカニズムを使用している場合でも、記述子の使用は問題ありません。
- ためらうことなく、フレームワーククラスを一般化されたものとして宣言します。 実行時にはそのようなものではないという事実にもかかわらず、この手法により、フレームワークのいくつかの要素のタイプをより正確に判別できますが、実行時エラーは簡単に回避できます。 (私たちは、フレームワークがジェネリック型の組み込みサポートを徐々に追加し、
typing.Generic
から対応するクラスを明示的に継承することをtyping.Generic
ます。)
最近リリースされたmypyプラグイン
すでに人気のあるPythonフレームワーク用のプラグインがいくつかあります。 上記のSQLAlchemyプラグインとは別に、スタブと組み込みmypyプラグインを含むその他の注目すべきサンプルパッケージには、 DjangoおよびZopeインターフェース用のスタブが含まれています。 これらのプロジェクトで積極的な作業が進行中です。
スタブパッケージとプラグインパッケージのインストールと接続
pipを使用して、 mypyおよび/またはスタブのプラグインパッケージをmypyが既にインストールされている仮想環境にインストールします。
$ pip install sqlalchemy-stubs
Mypyはインストールされたスタブを自動的に検出します。 インストールしたプラグインを接続するには、mypy.ini(またはユーザー設定ファイル)に直接それらを含めます:
[mypy] plugins = sqlmypy, mypy_django_plugin.main
mypyプラグインの開発とスタブの作成
使用するフレームワーク用のスタブとプラグインのパッケージを開発する場合、sqlalchemy-stubsリポジトリをテンプレートとして使用できます。
setup.py
、データ駆動型テストを使用したテストインフラストラクチャ、およびプラグイン用のフックセット(プラグインフック)を含むプラグインクラスの例が含まれてい
setup.py
。 stubgenを使用して、 mypyに付属するスタブを自動的に生成し、使用を開始することをお勧めします。
Stubgen
は
mypy 0.670
改善されました。
mypyプラグインシステムの詳細については、 ドキュメントをご覧ください 。 また、記事で説明したプラグインのソースコードをインターネットで検索することもできます。 質問がある場合は、 ここで質問できます 。
4月15日は、モスクワPythonコミュニティの主催者の1人であるVladimir Filonovが開催するコースの無料のオープンウェビナーになります。 そして今、翻訳された資料に対するあなたのコメントを待っています。