明日から使えるmypy導入ガイド
mypyとは、Pythonに型の“安全柵”を設け、バグの芽を実行前に摘み取る静的型チェッカーである。
この記事で得られること
- 明日から迷わずmypyを導入・運用するための最短ルート。
- チームでの合意形成と段階的な厳格化のコツ。
- 既存コードに痛み少なく型を足していく実践テクニック。
なぜ今mypyか(メリット)
mypyは「型注釈(type hints)」を静的に検査し、型不一致や未定義アクセスなどを実行前に指摘する。これによりレビュー負荷やデバッグ時間が減り、変更の可視性も上がる。中級者にとっては「壊さない自信」をコードベースに埋め込む装置だ。
効果の具体例
- リファクタ時に関数シグネチャ変更の波及漏れを早期検出。
- 動的分岐の取り違い(
Optional
やUnion
の未ハンドリング)を警告。 - IDE補完の精度が上がり、読みやすさ・保守性が向上。
他ツールとの比較
下表は「Python向け型チェッカー」の横比較。用途が同じ層のものは表で並べて判断する。
ツール | 特徴 | 強み | 弱み | 向き |
---|---|---|---|---|
mypy | 事実上の標準的存在、豊富な設定 | 生態系が充実、型整合性に厳密 | 学習コストや警告の多さ | サーバ・バッチ、長寿命コード |
Pyright | 高速、VS Code親和 | インクリメンタルが速い | 設定差異に注意 | クライアント/ツール併用 |
pytype | Google製 | 推論が強め | セットアップが大変 | 社内標準がある場合 |
導入の前提チェック
- Python 3.8+を推奨(
typing
まわりが安定)。 - 仮想環境(venv/poetry/conda)で依存を分離。
- 型注釈は段階導入で十分。最初から「完全カバー」は目指さない。
最短導入レシピ
-
インストール
pip install mypy typing-extensions
-
試しにチェック
mypy path/to/your_package
-
最小設定ファイルを置く(リポジトリ直下に
mypy.ini
)[mypy] python_version = 3.10 warn_return_any = True warn_unused_ignores = True no_implicit_optional = True disallow_untyped_defs = True show_error_codes = True pretty = True
-
まずは「警告は拾うが止めない」運用(CIを
--ignore-missing-imports
で回し、後述の段階的厳格化へ)。
設定の王道(理解しておくと強い主要オプション)
-
disallow_untyped_defs
: 型無注釈の関数定義を禁止。まずここから。 -
no_implicit_optional
: 引数のデフォルトNone
を暗黙Optional
にしない。バグ温床を除去。 -
warn_return_any
:Any
を返す関数に注意喚起。型の穴を塞ぐ指標になる。 -
warn_unused_ignores
: 不要な# type: ignore
を検出。ノイズの掃除。 -
strict = True
: 多数の厳格化フラグを一括ON。段階導入で最終到達点に。
mypy.ini
テンプレ(段階導入向け)
[mypy]
python_version = 3.10
warn_return_any = True
warn_unused_ignores = True
no_implicit_optional = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
check_untyped_defs = True
show_error_context = True
show_error_codes = True
pretty = True
ignore_missing_imports = True
# モジュール単位の厳格化例
[mypy-your_pkg.core.*]
disallow_untyped_defs = True
disallow_any_generics = True
[mypy-your_pkg.legacy.*]
# 旧来コードは緩めにし、徐々に締める
disallow_untyped_defs = False
段階的厳格化のロードマップ
一気にstrict
へ振ると反発が起きやすい。プロダクトリスクの高い領域から締め、周辺へ波及させるのが現実的。
段階 | 目的 | 主な設定 | 成功判定 |
---|---|---|---|
Lv1 | まず止血 | disallow_untyped_defs, no_implicit_optional | 新規/変更関数に型が付く |
Lv2 | 穴埋め | warn_return_any, check_untyped_defs | 重要経路のAny が消える |
Lv3 | 網羅性 | strict(の一部を選択) | 例外的ignore だけが残る |
既存コードに型を足すベストプラクティス
- 「外周から内側へ」。API境界、DTO(データ転送オブジェクト)、公開関数から注釈する。
-
TypedDict
やProtocol
で“構造的型”を使い、辞書やduck typingを安全化。 - 数値・文字列の区別が曖昧なら
NewType
で意味単位を分ける。 - 返り値が多態なら
Union
/Literal
/Enum
で分岐を明示。 - 長い関数は分割→それぞれに注釈。mypyは小さく確かめるほど効く。
サンプル(導入前→後)
# before
def fetch(user, limit=None):
data = api.get(user, limit)
return data or []
# after
from typing import Optional, Sequence, TypedDict
class Row(TypedDict):
id: int
name: str
def fetch(user: str, limit: Optional[int] = None) -> Sequence[Row]:
data: Optional[list[Row]] = api.get(user, limit)
return data or []
失敗しがちなポイントと対処
-
Any
の増殖: 一時的にAny
で塞ぐのはOKだが、# TODO(type)
タグで返済計画を明文化。 -
外部ライブラリの型: スタブがない場合は
types-xxx
パッケージやpyright
の型定義を参考に自作stubs/
を置く。 -
type: ignore
の乱用: 期限と理由を書く(# type: ignore[call-arg] 2025-09-30: 互換層削除まで
)。 -
ランタイムとの乖離:
assert isinstance(x, Foo)
などのガードを置き、静的保証と実行時保証を一致させる。
CI/CDへの組み込み
- ステップを分ける:
lint
(ruff/flake8)→typecheck
(mypy)→test
(pytest)。 - 失敗時は原因を短文で出すスクリプト化(ノイズを減らす)。
- キャッシュを使いCI時間を抑制(
--cache-dir
)。 - 差分チェック:修正差分だけを厳しめに通す運用も有効。
mypy your_pkg --show-column-numbers --cache-dir .mypy_cache
チーム合意のテンプレ(運用ルールを表で一望)
項目 | ルール | 理由 |
---|---|---|
新規コード | 関数定義に必ず型注釈 / from __future__ import annotations 有効化 |
未来の負債回避 |
変更コード | 触れた関数の返り値・引数を型で確定 | リファクタ耐性 |
ライブラリ | 型スタブが無い場合はPR時にstub追加 | 継続的改善 |
例外 |
ignore はID付与+期限 |
放置防止 |
検査粒度 | 重要モジュールはstrict 相当 |
品質の山を作る |
よく使う補助テク
-
from __future__ import annotations
で前方参照や循環参照を簡潔化。 -
typing_extensions
で前方機能を前倒し利用。 - dataclassの不変化(
frozen=True
)で“値オブジェクト”を型と一緒に堅くする。 -
Literal
で設定値の取りうる集合を閉じる。 -
Final
で定数を明示し、差し替え事故を防止。
まとめ(次の一手)
-
pip install mypy
して最小mypy.ini
を置く。 - 重要パスから
disallow_untyped_defs
で締める。 -
warn_return_any
で穴を見つけ、TypedDict/Protocol/NewType
で埋める。 - CIに組み込み、差分を厳格化。
- 月次で厳格度を一段上げ、年内に“主要モジュールstrict”を達成。
このガイドをそのままコミットメッセージとPRテンプレに転記すれば、明日からチームの型安全が動き出す。
FAQ(現場で出る質問)
-
Q: 速度は落ちない?
A: mypyは開発時だけ動く静的解析。実行速度には影響しない。CIでは並列化とキャッシュで待ち時間を抑えられる。 -
Q: 動的なコードはどうする?
A: 反射的な処理はProtocol
やTypedDict
で“構造”を表現し、境界で型を固定してから内部へ渡す。 -
Q: NumPy/Pandasは?
A: 型定義は整備が進んでいる。まずは引数・返り値をSequence
やMapping
など抽象に寄せ、内部は最小限の注釈で進める。
導入方式の比較
方式 | 概要 | 初期コスト | 維持コスト | メリット | デメリット |
---|---|---|---|---|---|
リポジトリ一括 | ルートで一律設定し全体を検査 | 中 | 低 | ルールが単純 | 初期エラーが多い |
モジュール段階 | 重要フォルダから順次strict 化 |
低 | 中 | 痛みが小さい | ばらつく |
フェンス方式 | 境界層にAdapterを置き型固定 | 中 | 中 | 外界の揺れを遮断 | 設計負荷 |
現場の小技(差分重視の運用)
- 変更ファイルだけmypyを実行、全体検査は夜間ジョブへ。
- メトリクス(
error count
/Any count
)を記録