Python の型ヒント入門 — 基本型からジェネリクスまで

概要

Python は動的型付け言語ですが、3.5 以降で導入された「型ヒント(type hints)」を使うと、関数や変数に期待する型を注釈として書けます。型ヒントは実行時には基本的に無視されますが、mypy などの静的解析ツールやエディタの補完・警告に活用でき、大規模化したコードの安全性と可読性を大きく改善します。

この記事では、基本型の注釈から OptionalUnion、コレクション型、そしてジェネリクス(総称型)までを、動く最小コードとともに順を追って解説します。

環境

  • Python 3.10 以降(新記法 X | Ylist[int] を前提)
  • 型チェッカー: mypypip install mypy
python --version
# Python 3.11.x
pip install mypy
mypy your_script.py

3.9 以前では一部の記法が使えないため、from __future__ import annotationstyping モジュールで代替します。

型ヒントは実行時には効かない

最初に押さえるべき最重要ポイントです。型ヒントは実行時の挙動を変えません。

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 を導入し、変更の多いモジュールから段階的に型を付けていくのがおすすめです。

型ヒントは「未来の自分と同僚への設計ドキュメント」でもあります。小さく始めて、コードの安心感を育てていきましょう。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください