Python の型ヒント入門 — 基本型からジェネリクスまで
概要
Python は動的型付け言語ですが、3.5 以降で導入された「型ヒント(type hints)」を使うと、関数や変数に期待する型を注釈として書けます。型ヒントは実行時には基本的に無視されますが、mypy などの静的解析ツールやエディタの補完・警告に活用でき、大規模化したコードの安全性と可読性を大きく改善します。
この記事では、基本型の注釈から Optional・Union、コレクション型、そしてジェネリクス(総称型)までを、動く最小コードとともに順を追って解説します。
環境
- Python 3.10 以降(新記法
X | Yやlist[int]を前提) - 型チェッカー:
mypy(pip install mypy)
python --version # Python 3.11.x pip install mypy mypy your_script.py
3.9 以前では一部の記法が使えないため、from __future__ import annotations や typing モジュールで代替します。
型ヒントは実行時には効かない
最初に押さえるべき最重要ポイントです。型ヒントは実行時の挙動を変えません。
def add(a: int, b: int) -> int:
return a + b
# 実行時には型チェックされないため、これは動いてしまう
print(add("x", "y")) # -> "xy"
このコードは実行するとエラーになりませんが、mypy にかけると型の不一致として警告されます。
$ mypy sample.py sample.py:5: error: Argument 1 to "add" has incompatible type "str"; expected "int" [arg-type]
つまり型ヒントは「実行を止める仕組み」ではなく「静的解析とツール支援のための情報」です。
基本型の注釈
name: str = "Alice"
age: int = 30
height: float = 165.5
is_admin: bool = False
def greet(name: str) -> str:
return f"Hello, {name}"
def log(message: str) -> None: # 戻り値がない関数は None
print(message)
Optional と Union
値が「ある型 または None」になり得る場合は Optional を使います。
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
users = {1: "Alice", 2: "Bob"}
return users.get(user_id) # 見つからなければ None
Optional[str] は str | None と同義です。Python 3.10 以降なら新記法が読みやすいです。
def show(user: str | None) -> None:
if user is None:
print("not found")
return
# ここでは user は str に絞り込まれている(型ナローイング)
print(user.upper())
複数の型を取り得る場合は Union(新記法 |)を使います。
def to_int(value: int | str) -> int:
return int(value)
コレクション型
リストや辞書には要素の型まで書けます。3.9 以降は組み込み型をそのまま使えます。
scores: list[int] = [90, 85, 100]
user_map: dict[int, str] = {1: "Alice", 2: "Bob"}
point: tuple[int, int] = (10, 20)
tags: set[str] = {"python", "typing"}
def group_by_len(words: list[str]) -> dict[int, list[str]]:
result: dict[int, list[str]] = {}
for w in words:
result.setdefault(len(w), []).append(w)
return result
読み取り専用で汎用的に受けたい場合は、list より抽象的な Iterable / Sequence が便利です。
from collections.abc import Iterable
def total(scores: Iterable[int]) -> int: # list でも tuple でも受けられる
return sum(scores)
ジェネリクス(総称型)
ジェネリクスは「型を引数として受け取る」仕組みで、要素の型が呼び出し側で決まる汎用関数・クラスを型安全に書けます。
from typing import TypeVar
T = TypeVar("T")
def first(items: list[T]) -> T:
return items[0]
n = first([1, 2, 3]) # n は int と推論される
s = first(["a", "b"]) # s は str と推論される
Any を使うと型情報が失われますが、TypeVar なら入出力の型がつながります。
ジェネリッククラス(3.12 以降の新構文)
class Stack[T]:
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
stack: Stack[int] = Stack()
stack.push(1)
value = stack.pop() # value は int
3.11 以前では typing.Generic を使った旧構文になります。
from typing import Generic, TypeVar
T = TypeVar("T")
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
確認方法
わざと型を間違えたコードを用意し、警告が出ることを確認します。
# check.py
def add(a: int, b: int) -> int:
return a + b
result: str = add(1, 2) # int を str に代入 → エラーになるはず
$ mypy check.py check.py:4: error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment] Found 1 error in 1 file (checked 1 source file)
エディタ(VS Code + Pylance など)でも同じ箇所に波線が表示されます。
注意点
- 型ヒントは実行時の型安全を保証しない。実行時検証が必要なら
pydanticなどを併用します。 Anyは型チェックを無効化する型です。乱用は避けます。- 3.9 以前では
typing.List/typing.Unionを使うか、from __future__ import annotationsを併用します。 - 段階的に導入できます。変更頻度が高いモジュールから型を付けていくのが現実的です。
まとめ
- 型ヒントは「実行時に効かない・静的解析で効く」情報である。
- 基本型 →
Optional/Union→ コレクション型 → ジェネリクスの順で理解すると無理がない。 TypeVarとジェネリッククラスで、汎用的なコードも型安全に書ける。- まずは
mypyを導入し、変更の多いモジュールから段階的に型を付けていくのがおすすめです。
型ヒントは「未来の自分と同僚への設計ドキュメント」でもあります。小さく始めて、コードの安心感を育てていきましょう。
