明日から使える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)を記録