閉じる

Python型ヒントの書き方と使い方: typingとmypyまで

型ヒントはPythonの読みやすさと保守性を大きく高める道具です。

エディタ補完の精度が上がり、関数の意図が明確になります。

さらにmypyなどの静的型チェッカーと組み合わせることで、実行前にバグの芽を摘み取れます。

本稿では型ヒントの基本からtypingの主要機能、mypyの実践導入まで、初心者でも段階的に理解できるよう丁寧に解説します。

目次 [ close ]

Python型ヒントの基本とメリット

型ヒントとは

型ヒントは、変数や関数の引数・戻り値に対して、期待する型を注釈として記述する仕組みです。

Pythonは動的型付き言語ですが、型ヒントは実行時の動作を変えず、開発支援ツールや静的解析に情報を提供します。

たとえば、リストの中身がintであることをlist[int]のように書きます。

可読性が上がる理由

型ヒントがあると、コードを読んだ瞬間にデータの形が理解できます。

引数や戻り値の型が明示されるため、関数の意図が自然言語の説明なしでも伝わります。

レビュー時の質問が減り、変更の影響範囲も推測しやすくなります

エディタの補完も強化され、関数呼び出し時のミスが減ります。

初心者が押さえるポイント

最初は変数・引数・戻り値に基本型を付けることから始めます。

次にコレクションの中身の型、OptionalやUnionの使い分け、そして辞書の構造にTypedDictを使う、と段階を踏むのが効果的です。

最初から完璧を目指さず、段階的に導入することが成功のコツです。

PEP484に基づく記法

型ヒントはPEP484で標準化されました。

古い記法ではtypingのジェネリック型List[int]などを使いますが、Python 3.9以降は後述の組み込みジェネリックlist[int]が推奨です。

Python 3.9以降の組み込みジェネリック(list[int])

Python 3.9から、組み込みコレクション型自体がジェネリックになりました。

これは読みやすく、依存も減るため推奨です。

Python
# 推奨 (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と同義です。

Python
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では明示的に書くと安全です。

Python
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で再代入を禁止できます(静的解析レベル)。

Python
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を書きます。

デフォルト引数がある場合も同様です。

Python
def greet(name: str) -> None:
    print(f"Hello, {name}!")

greet("Alice")
実行結果
Hello, Alice!

コレクション型(list, dict, set, tuple)

中身の型まで明示します。

キーと値を持つdictはdict[K, V]です。

Python
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, …])

タプルは長さと各要素の型を指定できます。

可変長は省略記号を使います。

Python
point2d: tuple[float, float] = (1.0, 2.0)
many_ints: tuple[int, ...] = (1, 2, 3, 4)  # 任意個のint

Callableの書き方とラムダ

関数型はCallable[[ArgTypes...], ReturnType]で表します。

ラムダも同様の型を満たします。

Python
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を使うとツールが確実に識別できます。

Python
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です。

Python
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のフィールドに型を付けると、生成子や補完が賢くなります。

Python
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で表せます。

必須・任意キーの区別もできます。

Python
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)

型引数を受けるクラスや関数はジェネリクスで表現します。

Python
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は構造的部分型を表し、メソッドが揃っていれば互換とみなします。

ダックタイピングを安全に活用できます。

Python
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除去なども可能です。

Python
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は最も広く使われる静的型チェッカーです。

プロジェクト毎に導入します。

Shell
python -m pip install mypy

基本の実行方法(mypy file.py)

ファイルやディレクトリを指定して実行します。

Shell
mypy app.py
mypy src/

設定ファイル(mypy.ini/pyproject.toml)

プロジェクトのルートに設定を置くと、コマンドが簡潔になります。

pyproject.tomlに集約するのが近年の主流です。

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
INI
; mypy.ini の例
[mypy]
python_version = 3.10
strict = True

主なオプション(strictなど)

strictは複数の厳格オプションを一括で有効化します。

段階導入ではdisallow_untyped_defsno_implicit_optionalなどから始めると良いです。

最初からstrictは辛い場合が多いため、徐々に広げます。

エラーの読み方と直し方

mypyの出力は行番号と推論した型を示します。

実際の例を見てみます。

Python
# 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パッケージを使います。

Shell
python -m pip install types-requests

スタブファイル(.pyi)とstubgen

自作ライブラリに外部公開用の型を付ける時はスタブが便利です。

既存コードから雛形を生成できます。

Shell
stubgen -m your_package -o stubs/

生成された.pyiを修正し、配布物に含めます。

Pythonバージョン設定と互換性

mypyのpython_versionを実行環境に合わせます。

3.9ではlist[int]が使えますが、3.8ターゲットではfrom __future__ import annotationstyping.Listを検討します。

実行バージョンと型チェックの前提を一致させることが重要です。

pre-commitとCIで自動化

コミット前に自動実行すると品質が安定します。

GitHub ActionsなどのCIにも組み込みましょう。

YAML
# .pre-commit-config.yaml の例
repos:
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.11.2
    hooks:
      - id: mypy
        additional_dependencies: [types-requests]
YAML
# 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で連携すると快適です。

JSON
// .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が拡散しにくくなります。

Python
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に組み込み、エディタ補完とあわせて日常の開発に自然に溶け込ませましょう。

完璧主義ではなく段階的導入を心がけることで、チーム全体の生産性と安心感が着実に向上します。

Python 実践TIPS - コーディング効率化・Pythonic
この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!