型ヒントはPythonの読みやすさと保守性を大きく高める道具です。
エディタ補完の精度が上がり、関数の意図が明確になります。
さらにmypyなどの静的型チェッカーと組み合わせることで、実行前にバグの芽を摘み取れます。
本稿では型ヒントの基本からtypingの主要機能、mypyの実践導入まで、初心者でも段階的に理解できるよう丁寧に解説します。
Python型ヒントの基本とメリット
型ヒントとは
型ヒントは、変数や関数の引数・戻り値に対して、期待する型を注釈として記述する仕組みです。
Pythonは動的型付き言語ですが、型ヒントは実行時の動作を変えず、開発支援ツールや静的解析に情報を提供します。
たとえば、リストの中身がintであることをlist[int]
のように書きます。
可読性が上がる理由
型ヒントがあると、コードを読んだ瞬間にデータの形が理解できます。
引数や戻り値の型が明示されるため、関数の意図が自然言語の説明なしでも伝わります。
レビュー時の質問が減り、変更の影響範囲も推測しやすくなります。
エディタの補完も強化され、関数呼び出し時のミスが減ります。
初心者が押さえるポイント
最初は変数・引数・戻り値に基本型を付けることから始めます。
次にコレクションの中身の型、OptionalやUnionの使い分け、そして辞書の構造にTypedDictを使う、と段階を踏むのが効果的です。
最初から完璧を目指さず、段階的に導入することが成功のコツです。
- 関連記事:変数の使い方完全入門
- 関連記事:関数の基本(def): 書き方と基本的な使い方
PEP484に基づく記法
型ヒントはPEP484で標準化されました。
古い記法ではtyping
のジェネリック型List[int]
などを使いますが、Python 3.9以降は後述の組み込みジェネリックlist[int]
が推奨です。
Python 3.9以降の組み込みジェネリック(list[int])
Python 3.9から、組み込みコレクション型自体がジェネリックになりました。
これは読みやすく、依存も減るため推奨です。
# 推奨 (Python 3.9+)
numbers: list[int] = [1, 2, 3]
# 互換性重視 (3.8以下)
from typing import List
numbers_legacy: List[int] = [1, 2, 3]
Python 3.10のUnion演算子(|)とOptional
Union型はX | Y
と書けるようになりました。
OptionalはUnion[T, None]
の短縮でT | None
と同義です。
def to_int(s: str | None) -> int | None:
if s is None:
return None
return int(s)
from __future__ import annotationsの効果
このimportを使うと、型注釈が文字列として遅延評価されます。
循環参照や前方参照が容易になり、評価タイミングの問題を避けられます。
Python 3.11以降ではデフォルトで有効ですが、3.8〜3.10では明示的に書くと安全です。
from __future__ import annotations
class Node:
def __init__(self, value: int, next: Node | None = None) -> None:
self.value = value
self.next = next
Docstringとの違い
Docstringは説明文、型ヒントは構造情報と考えると整理できます。
両者は補完し合います。
観点 | 型ヒント | Docstring |
---|---|---|
目的 | 構造を機械可読に表す | 意図や使い方を人間向けに説明 |
ツール連携 | mypy, Pyright, IDE補完 | Sphinx, IDE ホバー表示 |
実行時影響 | なし | なし |
書く場所 | シグネチャ横 | 三連クォート内 |
推奨 | 併用する | 併用する |
型ヒントの書き方とtypingの主要機能
変数の型注釈と定数(Final)
変数にはname: type
で注釈を書きます。
定数はFinal
で再代入を禁止できます(静的解析レベル)。
from typing import Final
pi: Final[float] = 3.14159 # 定数として扱う
radius: float = 2.0
# pi = 3.0 # mypy: "Final"に再代入は不可
area: float = pi * radius * radius
print(area)
12.56636
関数の引数・戻り値・Noneの型
戻り値がない関数は-> None
を書きます。
デフォルト引数がある場合も同様です。
def greet(name: str) -> None:
print(f"Hello, {name}!")
greet("Alice")
Hello, Alice!
コレクション型(list, dict, set, tuple)
中身の型まで明示します。
キーと値を持つdictはdict[K, V]
です。
names: list[str] = ["A", "B"]
scores: dict[str, int] = {"A": 90, "B": 80}
unique: set[int] = {1, 2, 3}
pair: tuple[str, int] = ("age", 20)
タプルの固定長と可変長(tuple[int, …])
タプルは長さと各要素の型を指定できます。
可変長は省略記号を使います。
point2d: tuple[float, float] = (1.0, 2.0)
many_ints: tuple[int, ...] = (1, 2, 3, 4) # 任意個のint
Callableの書き方とラムダ
関数型はCallable[[ArgTypes...], ReturnType]
で表します。
ラムダも同様の型を満たします。
from collections.abc import Callable # Python 3.12ではtypingからでも可
# 整数を受け取り文字列を返す関数
Formatter = Callable[[int], str]
def apply_and_show(x: int, fn: Formatter) -> None:
print(fn(x))
apply_and_show(10, lambda n: f"value={n}")
value=10
型エイリアス(type Alias)
長い型に名前を付けると読みやすくなります。
Python 3.10+ではTypeAlias
を使うとツールが確実に識別できます。
from typing import TypeAlias
UserId: TypeAlias = int
Name: TypeAlias = str
UserRecord: TypeAlias = tuple[UserId, Name]
def show_user(u: UserRecord) -> None:
print(u)
show_user((123, "Carol"))
(123, 'Carol')
Union, Optional, Literalの使い分け
複数候補はUnion、Noneを許すならOptional、限定集合ならLiteralです。
from typing import Literal
def render_align(text: str, align: Literal["left", "center", "right"]) -> str:
if align == "left":
return text.ljust(10)
if align == "center":
return text.center(10)
return text.rjust(10)
print(render_align("X", "center"))
X
dataclassとクラスの型注釈
クラス属性やdataclassのフィールドに型を付けると、生成子や補完が賢くなります。
from dataclasses import dataclass
@dataclass
class Product:
id: int
name: str
price: float
p = Product(1, "Book", 9.99)
print(p.name, p.price)
Book 9.99
辞書の構造はTypedDictで表現
外部APIのJSONのようにキーが固定の辞書はTypedDict
で表せます。
必須・任意キーの区別もできます。
from typing import TypedDict, NotRequired
class UserTD(TypedDict):
id: int
name: str
email: NotRequired[str] # 任意キー
def to_email(u: UserTD) -> str | None:
return u.get("email")
print(to_email({"id": 1, "name": "A"}))
print(to_email({"id": 2, "name": "B", "email": "b@example.com"}))
None
b@example.com
ジェネリクス(TypeVar, Generic)
型引数を受けるクラスや関数はジェネリクスで表現します。
from typing import TypeVar, Generic
T = TypeVar("T")
class Box(Generic[T]):
def __init__(self, value: T) -> None:
self.value = value
def get(self) -> T:
return self.value
int_box = Box[int](10)
str_box = Box[str]("hi")
print(int_box.get(), str_box.get())
10 hi
インターフェースはProtocolで表現
Protocolは構造的部分型を表し、メソッドが揃っていれば互換とみなします。
ダックタイピングを安全に活用できます。
from typing import Protocol
class Reader(Protocol):
def read(self, n: int = ...) -> bytes: ...
class FileLike:
def read(self, n: int = -1) -> bytes:
return b"data"
def load(r: Reader) -> bytes:
return r.read()
print(load(FileLike()))
b'data'
Any, cast, assertの使い所
Any
は何でも通る逃げ道です。
最小限に留め、境界で使います。
cast
は開発者が型を保証する時に限定的に使い、assert
でNone除去なども可能です。
from typing import Any, cast
data: Any = {"count": "10"} # 外部入力の境界ではやむを得ずAny
count = int(cast(dict[str, str], data)["count"]) # 明示的に整形
maybe_text: str | None = "ok"
assert maybe_text is not None # 以降はstrとして扱える
print(maybe_text.upper())
OK
mypyの使い方と設定
mypyのインストール(pip)
mypyは最も広く使われる静的型チェッカーです。
プロジェクト毎に導入します。
python -m pip install mypy
基本の実行方法(mypy file.py)
ファイルやディレクトリを指定して実行します。
mypy app.py
mypy src/
設定ファイル(mypy.ini/pyproject.toml)
プロジェクトのルートに設定を置くと、コマンドが簡潔になります。
pyproject.tomlに集約するのが近年の主流です。
# pyproject.toml
[tool.mypy]
python_version = "3.11"
warn_unused_ignores = true
warn_redundant_casts = true
disallow_untyped_defs = true
no_implicit_optional = true
; mypy.ini の例
[mypy]
python_version = 3.10
strict = True
主なオプション(strictなど)
strict
は複数の厳格オプションを一括で有効化します。
段階導入ではdisallow_untyped_defs
やno_implicit_optional
などから始めると良いです。
最初からstrictは辛い場合が多いため、徐々に広げます。
エラーの読み方と直し方
mypyの出力は行番号と推論した型を示します。
実際の例を見てみます。
# sample.py
def add(a: int, b: int) -> int:
return a + b
x = add(1, "2") # 型ミス
$ mypy sample.py
sample.py:5: error: Argument 2 to "add" has incompatible type "str"; expected "int" [arg-type]
Found 1 error in 1 file (checked 1 source file)
エラーは「第2引数はstrだがintを期待」と読めます。
“どの位置が、どの型で、何を期待したか”を意識すると対処が早くなります。
段階的導入の進め方
新規コードから型を付け、既存コードはAnyを認めつつ# type: ignore[error-code]
で必要最小限に抑えます。
サブモジュール単位でstrictを有効化し、テストが通ったら範囲を広げていくと安全です。
サードパーティ型定義(typeshed, types-xxx)
多くのライブラリはtypeshedに型定義が同梱されています。
未同梱のものはtypes-xxx
パッケージを使います。
python -m pip install types-requests
スタブファイル(.pyi)とstubgen
自作ライブラリに外部公開用の型を付ける時はスタブが便利です。
既存コードから雛形を生成できます。
stubgen -m your_package -o stubs/
生成された.pyi
を修正し、配布物に含めます。
Pythonバージョン設定と互換性
mypyのpython_version
を実行環境に合わせます。
3.9ではlist[int]
が使えますが、3.8ターゲットではfrom __future__ import annotations
やtyping.List
を検討します。
実行バージョンと型チェックの前提を一致させることが重要です。
pre-commitとCIで自動化
コミット前に自動実行すると品質が安定します。
GitHub ActionsなどのCIにも組み込みましょう。
# .pre-commit-config.yaml の例
repos:
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.2
hooks:
- id: mypy
additional_dependencies: [types-requests]
# GitHub Actions の例
name: type-check
on: [push, pull_request]
jobs:
mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: python -m pip install -e . mypy
- run: mypy .
開発フローに組み込む実践Tips
エディタ連携(VS Codeでmypy)
VS CodeではPylanceの型チェックとmypyを併用できます。
設定でpython.analysis.typeCheckingMode
を"basic"
または"strict"
にし、拡張機能ms-python.vscode-pylance
を有効化します。
mypyはターミナルや拡張mypy-type-checker
で連携すると快適です。
// .vscode/settings.json
{
"python.analysis.typeCheckingMode": "basic",
"python.linting.mypyEnabled": true, // 拡張により名称は変わる場合あり
"python.testing.pytestEnabled": true
}
Linterやフォーマッタ(ruff/flake8/black)と併用
ruffやflake8はスタイルとバグパターンを、blackは整形を担当します。
mypyは「型」、ruffは「スタイル」、blackは「整形」と役割分担を意識すると設定が整理されます。
ruffはmypyと競合しないため併用が一般的です。
ランタイム未チェックの注意点とテスト
型ヒントは実行時に強制されません。
mypyが通っても、実行時の値が想定外なら落ちます。
境界でのバリデーションやテストを合わせて実施してください。
pydanticやattrsは実行時検証に役立ちます。
動的データの境界での型付け(JSONや外部API)
外部データは信頼せず、受け取り直後に構造化すると安全です。
TypedDictやdataclassに変換してから内部に渡すと、以降のコードでAnyが拡散しにくくなります。
from typing import TypedDict
from dataclasses import dataclass
import json
class ArticleTD(TypedDict):
id: int
title: str
tags: list[str]
@dataclass
class Article:
id: int
title: str
tags: list[str]
def parse_article(raw: str) -> Article:
obj: ArticleTD = json.loads(raw) # 期待構造をまず型で表す
return Article(**obj)
print(parse_article('{"id":1,"title":"Type Hints","tags":["py","tips"]}'))
Article(id=1, title='Type Hints', tags=['py', 'tips'])
まとめ
型ヒントはPythonの柔軟さを損なわず、意図を機械にも人にも正確に伝えるための強力な道具です。
基礎では変数・引数・戻り値に型を付け、コレクションやUnion、Optional、Literalを使い分けます。
構造的インターフェースはProtocol、複雑な辞書はTypedDict、再利用には型エイリアスやジェネリクスが役立ちます。
mypyを導入してCIやpre-commitに組み込み、エディタ補完とあわせて日常の開発に自然に溶け込ませましょう。
完璧主義ではなく段階的導入を心がけることで、チーム全体の生産性と安心感が着実に向上します。