はじめに
「Pythonは動的型付けだから型なんて書かなくていい」
そう思ってた時期が私にもありました。
でも型ヒントを真面目に書くようになってから、実行前にバグが見つかるようになって、生産性が爆上がりした。
型ヒントなしだとこうなる
def calculate_total(items, tax_rate):
"""商品リストから合計金額を計算"""
total = 0
for item in items:
total += item["price"] * item["quantity"]
return total * (1 + tax_rate)
一見普通の関数。でも使う側がミスると...
# バグ1: tax_rateに文字列を渡す
calculate_total(products, "10%")
# => TypeError: unsupported operand type(s) for +: 'int' and 'str'
# バグ2: itemsに文字列を渡す
calculate_total("りんご3個", 0.1)
# => TypeError: string indices must be integers, not 'str'
# バグ3: Noneを渡す
calculate_total(None, 0.1)
# => TypeError: 'NoneType' object is not iterable
全部実行時エラー。本番環境で初めて気づくパターン。
型ヒントありだとこうなる
from typing import TypedDict
class Product(TypedDict):
name: str
price: int
quantity: int
def calculate_total(items: list[Product], tax_rate: float) -> float:
total = 0
for item in items:
total += item["price"] * item["quantity"]
return total * (1 + tax_rate)
mypyで静的解析:
$ mypy mypy_test.py
mypy_test.py:22: error: Argument 2 has incompatible type "str"; expected "float"
mypy_test.py:23: error: Argument 1 has incompatible type "str"; expected "list[Product]"
mypy_test.py:24: error: Argument 1 has incompatible type "None"; expected "list[Product]"
Found 3 errors in 1 file
実行する前にバグが見つかる!
型ヒント完全ガイド
基本の型
def greet(name: str) -> str:
return f"Hello, {name}!"
def add(a: int, b: int) -> int:
return a + b
def is_valid(flag: bool) -> bool:
return flag
Optional(None許容)
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
"""ユーザーを検索。見つからなければNone"""
users = {1: "Alice", 2: "Bob"}
return users.get(user_id)
# 使う側
user = find_user(1)
if user is not None: # 型ガード
print(user.upper()) # ここではstrとして扱える
Union(複数の型)
from typing import Union
def format_id(id_value: Union[int, str]) -> str:
if isinstance(id_value, int):
return f"ID-{id_value:05d}"
return f"ID-{id_value}"
# Python 3.10+では | 記法が使える
def format_id_new(id_value: int | str) -> str:
return f"ID-{id_value}"
リスト・辞書
def process_scores(scores: list[int]) -> dict[str, float]:
return {
"average": sum(scores) / len(scores),
"max": float(max(scores)),
"min": float(min(scores)),
}
コールバック関数
from typing import Callable
def apply_operation(
values: list[int],
operation: Callable[[int], int] # int -> int の関数
) -> list[int]:
return [operation(v) for v in values]
doubled = apply_operation([1, 2, 3], lambda x: x * 2)
# => [2, 4, 6]
ジェネリクス
from typing import TypeVar, Optional
T = TypeVar('T')
def first_element(items: list[T]) -> Optional[T]:
"""リストの最初の要素を返す"""
return items[0] if items else None
# 型が自動推論される
num = first_element([1, 2, 3]) # int | None
text = first_element(["a", "b"]) # str | None
TypedDict(辞書の構造定義)
from typing import TypedDict, NotRequired
class APIResponse(TypedDict):
status: str
data: dict
error: NotRequired[str] # オプショナルなキー
def handle_response(response: APIResponse) -> None:
print(f"Status: {response['status']}")
if 'error' in response:
print(f"Error: {response['error']}")
Literal(リテラル型)
from typing import Literal
def set_log_level(level: Literal["DEBUG", "INFO", "WARNING", "ERROR"]) -> None:
print(f"Log level: {level}")
set_log_level("INFO") # OK
set_log_level("VERBOSE") # mypyエラー!
Final(定数)
from typing import Final
MAX_CONNECTIONS: Final = 100
MAX_CONNECTIONS = 200 # mypyエラー!再代入禁止
dataclassと組み合わせると最強
from dataclasses import dataclass
from typing import Optional
from datetime import datetime
@dataclass
class User:
id: int
name: str
email: Optional[str] = None
created_at: datetime = datetime.now()
def is_guest(self) -> bool:
return self.email is None
user = User(id=1, name="Alice", email="alice@example.com")
print(user.is_guest()) # => False
- 型が明確
-
__init__,__repr__,__eq__が自動生成 - IDEの補完が効く
mypyの使い方
インストール
pip install mypy
実行
# 単一ファイル
mypy your_script.py
# ディレクトリ全体
mypy src/
# 厳格モード
mypy --strict your_script.py
pyproject.tomlで設定
[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_ignores = true
disallow_untyped_defs = true
VSCodeとの連携
settings.jsonに追加:
{
"python.analysis.typeCheckingMode": "basic",
"mypy-type-checker.args": ["--strict"]
}
よくある疑問
Q: 実行時に型チェックされる?
されません。型ヒントは静的解析ツール(mypy, Pyright)のためのもの。
実行時チェックが必要なら pydantic や beartype を使います。
Q: 外部ライブラリの型が無い
types-xxx パッケージをインストール:
pip install types-requests types-redis
Q: 型が複雑になりすぎる
型エイリアスを使う:
from typing import TypeAlias
UserId: TypeAlias = int
UserDict: TypeAlias = dict[UserId, str]
def get_user(users: UserDict, user_id: UserId) -> str | None:
return users.get(user_id)
まとめ
| メリット | 説明 |
|---|---|
| 🐛 バグ早期発見 | 実行前に型エラーを検出 |
| 📝 ドキュメント | 関数の使い方が明確に |
| 🔧 リファクタリング | 型の変更が影響する箇所がわかる |
| 💡 IDE補完 | 入力候補が正確に |
# 型ヒントを書く習慣をつけよう
def your_function(arg: int) -> str:
...