第4章 Python の型システムと型ヒントの基礎
Python は元来、変数の型を実行時に動的に判定する「動的型付け言語」です。
しかし、プロジェクトの規模が拡大するにつれて、「この関数は何を引数に受け取るのか」「戻り値は文字列なのか None もあり得るのか」といった情報がコードから読み取りづらくなり、実行時エラー(AttributeError や TypeError)の原因となっていました。
そこで Python 3.5 以降、コードの可読性と保守性を飛躍的に高めるために「型ヒント(Type Hints)」が導入されました。本章では、型ヒントの基本概念と日常的に使用する基本的な型注釈の書き方を解説します。
4.1 型ヒントの基本思想と漸進的型付け
Python の型ヒントは、コード内に型の注釈を記述できる仕組みですが、プログラムの実行時に強制されるものではありません。
Python のインタープリタは型ヒントを基本的に無視して実行するため、実行時パフォーマンスに悪影響を与えません。型ヒントの目的は以下の通りです:
- エディタの補完強化: VS Code などのエディタが正確なメソッド一覧やプロパティを予測・補完できるようにする。
- 自己文書化: ドキュメントを読まなくても、関数シグネチャを見るだけで入出力の仕様を理解できるようにする。
- 事前静的解析:
tyやpyrightなどの型チェッカーを用いることで、プログラムを実行する前に型の食い違いやバグを検出する。
すべてを一度に型付けする必要はなく、重要な関数や新しいモジュールから段階的に型を追加できる性質を 漸進的型付け(Gradual Typing) と呼びます。
4.2 変数と関数の基本アノテーション
型注釈の文法は、コロン : を用いて変数や引数の後ろに型名を記述し、戻り値には -> を使用します。
変数の型注釈
# 変数名: 型名 = 初期値
age: int = 25
user_name: str = "Alice"
is_active: bool = True
temperature: float = 23.5
通常の変数定義では、代入される値から推論されるため型注釈を省略することも多いですが、空のリストを初期化する場合など型が推論できない場面で役立ちます。
# 推論が難しい場合は明示する
scores: list[int] = []
関数の型注釈
関数の引数と戻り値には、必ず型注釈を付与することが強く推奨されます。
def calculate_tax(price: int, rate: float = 0.1) -> int:
"""税込み価格を計算して整数で返す。"""
return int(price * (1 + rate))
def notify_user(message: str) -> None:
"""戻り値がない(何も返さない)関数には None を指定する。"""
print(f"Notification: {message}")
戻り値が存在しない(値を return しない)関数には -> None を指定します。
4.3 コレクション型の注釈 (Python 3.9 以降)
リストや辞書、タプルなどのコレクションには、その要素がどのような型を持つのかを [...] で指定します。
Python 3.9 以降では、typing モジュールからインポートすることなく、標準の組み込み型(list, dict, tuple, set)をそのまま型注釈として利用できます。
# 文字列のリスト
names: list[str] = ["Alice", "Bob", "Charlie"]
# キーが文字列、値が整数の辞書
user_points: dict[str, int] = {
"Alice": 100,
"Bob": 80,
}
# 要素の型が決まっているタプル(固定長)
point: tuple[int, int] = (10, 20)
# 可変長で同一型のタプル(末尾に ... を指定)
coordinates: tuple[float, ...] = (1.5, 2.8, 3.2, 4.0)
# 重複のない要素の集合(セット)
unique_ids: set[int] = {1, 2, 3, 5}
4.4 ユニオン型と None の許容 (Python 3.10 以降)
変数が「複数の型のうちいずれか」を取り得る場合、ユニオン型を使用します。
Python 3.10 以降では、縦棒パイプ記号 | を使って簡潔に記述できます。
# int または float のいずれかを受け取る
def process_number(value: int | float) -> float:
return float(value) * 2.0
None を許容する型 (Optional)
データベースの検索結果など、「値が見つからない場合は None を返す」関数では、型に | None を付与します。
# 戻り値は str または None
def find_user_email(user_id: int) -> str | None:
if user_id == 1:
return "admin@example.com"
return None
従来の Python では from typing import Optional; Optional[str] と書いていましたが、Python 3.10 以降では str | None と書くのが現代の標準です。
呼び出し側で None チェックを行わずにメソッドを呼び出そうとすると、静的型チェッカーがエラーを報告してくれます。
email = find_user_email(2)
# 型チェッカーは以下の呼び出しを警告する (email が None の可能性があるため)
# print(email.lower())
# 正しい書き方: None チェックを行って型を絞り込む (Type Narrowing)
if email is not None:
print(email.lower())
4.5 型エイリアス (Python 3.12 以降の type 文)
複雑な型定義にわかりやすい名前を付けたい場合、型エイリアスを使用します。
Python 3.12 では、専用の type 文が導入されました。
# ユーザーID型と座標型の定義
type UserID = int
type Coordinate = tuple[float, float]
def get_location(user_id: UserID) -> Coordinate:
return (35.6895, 139.6917)
コードの意図が明確になり、型の変更が必要になった際も 1 箇所の修正で済みます。
4.6 型チェッカーによる静的検証
書いた型アノテーションが正しいかどうかは、型チェッカーを実行して検証します。代表的な型チェッカーには Astral 社が提供する高速な ty や、pyright などがあります。
uv を使って ty を開発依存関係に追加し、検証してみましょう:
uv add --dev ty
uv run ty check .
型チェッカーはコードを実行することなく、型の不一致や None 参照の危険性を自動で洗い出してくれます。