python-typing について
Python 3.12 以降のプロジェクトで、開発者やコーディングエージェントが参照できる型付けガイドです。現代的な構文や型チェック、データ構造の選び方を扱い、明示的な型と `Any` の抑制を重視します。
主な機能
- 01公開シグネチャに型注釈を付ける
- 02現代的な Python 型構文を使う
- 03`Any` より具体的な型を選ぶ
- 04ジェネリクスと `Protocol` を活用
- 05適切なデータ構造を選ぶ
- 06厳格な型チェックを設定
活用シーン
- 01Python プロジェクトの型付け規約を統一
- 02新しい関数やクラスの型注釈を設計
- 03広範な `Any` と型無視を見直す
- 04CI で厳格な型チェックを適用
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ aymericderbois/ skills --skill python-typingインストール後、Claude Code で/python-typingを入力して読み込まれたか確認します。
詳細
python-typing
変数、パラメーター、戻り値の型付けは不可欠です。次のことが可能になります:
- 実行前にバグを検出する
- コードを文書化する
- LLM がコードを理解しやすくする
- IDE でより優れた自動補完を利用する
対象:Python 3.12+
型チェッカー(プロジェクトの設定とインストール済みのツールに応じて選択します):
| ツール | + | − |
|---|---|---|
pyrefly (Meta, Rust) | 非常に高速、モダン | 新しく、エコシステムは発展途上 |
ty (Astral, Rust) | 非常に高速、uv/ruff に統合 | まだプレビュー段階 |
mypy --strict | 成熟しており、広く使われている標準 | やや遅い |
pyright (Microsoft) | 高速で、narrowing に優れる (Pylance) | Node に依存する |
用語集
以下で使う技術用語を、ここで一度だけ定義します:
- 型アノテーション — コードに記述する型(
age: int、-> User)。公式用語です(PEP 526)。 - 型チェッカー — アノテーションの整合性を確認するツールです(pyrefly、mypy など)。
- 型推論 — 明示的なアノテーションなしで、型チェッカーが自動的に推定する型です。
- ジェネリック — 入力の型を出力まで引き継ぐ型パラメーター(
T)です。 Protocol— 継承ではなく、その形(メソッド)によって定義される型です。検証付きの ダックタイピング です。- ダックタイピング — オブジェクトをクラスではなく、何ができるかによって受け入れることです。
Any— その型が使われている箇所で、型チェックを無効にする型です。cast— 型チェッカーに型を指定するためのものです。
ルール
1. すべての公開シグネチャに型を付ける
- 必ずすべての関数、メソッド、クラス属性に型を付けます。対象はパラメーター、戻り値、属性です。
- 必要に応じて、読みやすさが向上する場合は、関数やメソッドの内部のコードにも型を付けます(たとえば、戻り値がやや不明瞭な関数を文書化する場合)。
def get_user(user_id: str) -> User | None:
"""Renvoie l'utilisateur s'il existe, None sinon."""
...
def do_something(v: str) -> None:
"""
Le type de retour de process_value() n'est pas explicite, le nom de la fonction ne donne pas
d'information claire, on peut donc typer le résultat pour améliorer la lisibilité.
"""
result: int = process_value(v)
...
class UserRepository:
user: User | None
def __init__(self, db: Database) -> None: # -> None explicite sur __init__
self._db = db
async def save(self, user: User) -> User: ...
2. 古い typing エイリアスではなく、モダンな構文を使う
古い typing エイリアス(Optional、Union、List など)より、モダンな構文を必ず優先します。
古い構文は、古いバージョンの Python との後方互換性が必要な場合に限り使用します。
| 避けるもの | 使用するもの |
|---|---|
Optional[X] | X | None |
Union[A, B] | A | B |
List[X], Dict[K, V] | list[X], dict[K, V] |
Tuple[int, ...], Set[X] | tuple[int, ...], set[X] |
from typing import List, Dict | (インポート不要) |
3. Any を避け、より具体的な型を使う
Any は、その型が使われている箇所で型チェックを無効にします。
可能な場合は、Any をより具体的な型に置き換えます:
- 本当に未知のデータ →
object:使用前に型を絞り込む必要がありますが、問題なく使えます。 - 再利用可能なコンテナーや関数 → 型を保持するジェネリック(ルール 5)。
- 型付けされていないライブラリの出力 → 入力箇所で
cast(TypeVoulu, valeur)を使い、その後は適切な型を使います。 - 「特定のメソッドを持つもの」 →
Protocol(ルール 6)。
cast() は型を制御せずに指定します。実際に型が分かっている値に限って使い、正当なエラーを黙らせるためには決して使いません。その場合は、実際の型を修正します。
一時的なエラーを抑制する必要がある場合は、対象をコードで指定します:# type: ignore[return-value]。将来その行で発生するエラーまで隠してしまう、引数のない # type: ignore は決して使いません。
4. 不在を明示する
不在も型の一部です。暗黙の番兵値や型指定のない None ではなく、X | None として注釈を付けます。そうすることで、値を使う前に型チェッカーが絞り込み(is None、assert)を必須とします。
def find(user_id: str) -> User | None: # l'absence est dans la signature
...
u = find("42")
u.name # rejeté : u peut être None
if u is not None:
u.name # ok : restreint à User
5. ジェネリクス:型情報を保持する
ジェネリクスを使うと、複数の異なる型で動作する汎用的な型付けができます。PEP 695 の構文(3.12+)です。 通常は、関数やメソッドの入力型を戻り値の型まで引き継ぐために使います。
def first[T](items: list[T]) -> T:
# first([1, 2]) -> int
# first(["a"]) -> str
return items[0]
class Box[T]:
def __init__(self, value: T) -> None:
self._value = value
def get(self) -> T:
return self._value
6. Protocol:継承ではなく構造的部分型付け
Protocol は、継承を必須とせずに、期待される形(メソッド)を定義します。
具象クラスを所有していない場合や、特定のメソッドだけが必要な場合に使うのがおすすめです。
from typing import Protocol
class Readable(Protocol):
def read(self) -> bytes: ...
def consume(source: Readable) -> bytes: # accepte fichier, socket, BytesIO… sans lien d'héritage
return source.read()
ABC(明示的な継承)は、クラス階層を所有していて、実装を共有したい場合に使います。
7. 適切なデータ構造
dict[str, Any] を積み重ねるのではなく、データの性質に応じてコンテナーを選びます。
TypedDict— キーが既知の固定された dict(JSON ペイロード、API レスポンス)。dataclass— デフォルト値、振る舞い、または不変条件を持つオブジェクト。NamedTuple— 名前付きの、イミュータブルで軽量なタプル。Literal/Enum— 閉じた値の集合(Literal["asc", "desc"]):型チェッカーは集合外の値を拒否しますが、strではそうなりません。
8. 名前を付けて固定する
- 複雑で繰り返し使う型にはエイリアスを使います:
type Json = dict[str, "Json"] | list["Json"] | str | int | float | bool | None(PEP 695、3.12+)。繰り返し使うユニオン型には、分かりやすい名前を付けるのが適切です。 - 再代入できない定数には
Final - クラス属性(インスタンス属性との区別)には
ClassVar。 - 自身の型を返すメソッドには
Self(3.11+)を使います(-> Self)。サブクラスでも有効です。 - 別のメソッドをオーバーライドする想定のメソッドには
@override(3.12+、またはtyping_extensions)を付けます。親クラスでメソッド名が変更または削除されると、CI でエラーになります。
要注意のサイン — STOP
Anyを書こうとしている →object、ジェネリクス、Protocol、または対象を絞ったcastで代用できませんか?- コードを指定せずに
# type: ignoreを使っている →[code]を追加してください。そうしないと、将来のエラーを見逃します。 typingからOptional/List/Dictをインポートしている → 新しい構文(X | None、list、dict)に変更してください。- 正しいエラーを抑えるために
cast()を使っている → 型チェッカーではなく、型を修正してください。 - 注釈は丁寧に付けているのに、CI で厳格な型チェッカーを実行していない → 型付けに実効性がありません。