【Python】型ヒント(タイプヒント)の使い方|実行時に強制されない・list・Optional

型ヒント(タイプヒント、type hints)は、変数や引数、戻り値が「どんな型か」をコードに書き添える機能です。関数の引数にname: str、戻り値に-> strのように注釈を付けると、コードの意図が明確になり、エディタの補完やエラー検出も効くようになります。大規模な開発やチーム開発で、バグを未然に防ぐのに役立ちます。

最大の注意点は、型ヒントは実行時には強制されないことです。name: strと書いても、Pythonは実際に文字列が渡されたかをチェックしません。あくまで「ヒント(目印)」であり、型のチェックはmypyなどの別ツールで行います。この性質を知らないと「型を書いたのにエラーにならない」と戸惑います。この記事では、実機のPython 3.12で確認しながら、型ヒントを整理します。

先に結論

  • 引数はname: str、戻り値は-> str、変数はage: int = 30と書きます。
  • 型ヒントは実行時に強制されません。違う型を渡してもそのまま動きます。
  • リストや辞書はlist[int]dict[str, int](Python 3.9以降)と書けます。
  • Noneを許すときはstr | None(Python 3.10以降)またはOptional[str]です。
  • 型が正しいかはmypyなどのツールで静的にチェックします。
  • 注釈は__annotations__で確認でき、エディタの補完にも使われます。

型ヒントを付ける対象の関数(def)、型そのものの扱いは型変換list[int]などで使うリストもあわせて参考になります。

スポンサーリンク

型ヒントとは(基本の書き方)

関数では、引数名のあとに: 型引数リストのあとに-> 型で戻り値の型を書きます。デフォルト値と組み合わせるときはage: int = 0のようにします。

関数に型ヒントを付ける
# name は str、age は int(デフォルト0)、戻り値は str
def greet(name: str, age: int = 0) -> str:
    return f"{name}({age}歳)"

print(greet("田中", 30))   # 田中(30歳)

# 引数: int、戻り値: int
def add(a: int, b: int) -> int:
    return a + b

実機でも、greet("田中", 30)田中(30歳)を返しました。name: strは「nameは文字列のつもり」、-> strは「戻り値は文字列のつもり」という意味です。型ヒントを付けても動作は変わりませんが、コードを読む人やエディタにとって「ここには何が入るか」が明確になります。とくに引数が多い関数では、型ヒントがあると使い方がぐっと分かりやすくなります。

【最重要】実行時には強制されない

もっとも重要な性質がこれです。型ヒントと違う型の値を渡しても、Pythonはエラーにせず、そのまま実行します。型ヒントは「こうあるべき」という目印にすぎず、実行時のチェックは行われません。

型ヒントは強制されない
def greet(name: str, age: int = 0) -> str:
    return f"{name}({age}歳)"

# name に数値、age に文字列を渡しても…
print(greet(123, "文字列"))
#  → エラーにならず「123(文字列歳)」と動いてしまう

# Python は型ヒントを「チェック」しない。あくまでヒント
型ヒントはチェックではなく「ヒント」

実機で確認したところ、name: strage: intと書いた関数に、数値と文字列を逆に渡しても(greet(123, "文字列"))エラーにならず123(文字列歳)とそのまま動いてしまいました。これは、Pythonが型ヒントを実行時にチェックしないためです。型ヒントは、あくまでコードを読む人やツールのための「目印」であり、間違った型を渡しても止めてはくれません。「型を書いたのに、なぜか型違反でエラーにならない」と感じるのは、この仕様によるものです。実際に型の誤りを検出したいときは、次に紹介するmypyなどの静的解析ツールを使います。型ヒントは「ドキュメント+ツール連携」のためのもの、と理解しておくと混乱しません。

変数・リスト・辞書の型ヒント

変数にもage: int = 30のように型ヒントを付けられます。リストや辞書の中身の型は、list[int]dict[str, int]のように書きます(Python 3.9以降)。

変数・リスト・辞書の注釈
# 変数の型ヒント
age: int = 30
name: str = "佐藤"

# リストの中身が int(Python 3.9 以降)
def total(nums: list[int]) -> int:
    return sum(nums)

print(total([1, 2, 3]))   # 6

# 辞書: キーが str、値が int
scores: dict[str, int] = {"国語": 80, "数学": 95}

実機でも、list[int]と注釈した関数に[1, 2, 3]を渡して6が得られました。list[int]は「intを要素に持つリスト」、dict[str, int]は「キーがstr、値がintの辞書」という意味です。Python 3.9より前のバージョンでは、from typing import List, DictとしてList[int]Dict[str, int]と書く必要があります。新しいPythonでは、組み込みのlistdictをそのまま使えて簡潔です。

Optional(Noneを許す)とUnion

「文字列またはNone」のように複数の型を許す場合は、str | Noneと書きます(Python 3.10以降)。これはtypingモジュールのOptional[str]Union[str, None]と同じ意味です。

Optional と Union
# str または None(Python 3.10 以降の書き方)
def find(key: str) -> str | None:
    return None   # 見つからなければ None を返す、など

# typing モジュールを使う書き方(古いバージョンでも動く)
from typing import Optional, Union

def find2(key: str) -> Optional[str]:   # str | None と同じ
    return None

def f(x: Union[int, str]) -> int:       # int | str と同じ
    return 0

実機でも、str | NoneOptional[int]Union[int, str]の型注釈が問題なく使えました。Optional[str]は「strまたはNoneという意味で、「見つからなければNoneを返す」ような関数でよく使います。Union[int, str]は「intまたはstrです。Python 3.10以降では、str | Noneint | strのように|(パイプ)で書くほうが簡潔で読みやすく、こちらが推奨されています。古いバージョンを対象にする場合はtypingOptionalUnionを使います。

mypyで静的にチェックする

型ヒントを「実際に検査する」には、mypyなどの静的解析ツールを使います。プログラムを実行せずに、型ヒントと矛盾する箇所を事前に指摘してくれます。

mypy で型チェック
# インストール
# pip install mypy

# チェックの実行
# mypy script.py

# 例: greet(123, "文字列") のような型違反があると、
#   mypy は「str を期待しているのに int が渡された」と警告する
#   → 実行する前にバグに気づける

mypyは、pip install mypyでインストールし、mypy ファイル名で実行します。プログラムを動かす前に、型ヒントと食い違う箇所を検出してくれます。Pythonそのものは型ヒントをチェックしませんが、mypyを組み合わせることで、「型ヒントを書く意味」が生きてきます。VS Codeなどのエディタも、型ヒントをもとにリアルタイムで警告を出してくれます。型ヒントは、こうしたツールと組み合わせて初めて真価を発揮します。

__annotations__・なぜ書くのか

付けた型ヒントは__annotations__という属性で確認できます。型ヒントは実行を変えませんが、コードの可読性・エディタの補完・バグの早期発見という大きなメリットがあります。

__annotations__ で確認
def greet(name: str, age: int = 0) -> str:
    return f"{name}({age}歳)"

print(greet.__annotations__)
# {'name': <class 'str'>, 'age': <class 'int'>, 'return': <class 'str'>}

# 型ヒントはここに記録されている(実行には影響しない)

実機でも、greet.__annotations__{'name': str, 'age': int, 'return': str}と、付けた型ヒントが記録されているのを確認できました。型ヒントを書くメリットは、①コードを読む人に意図が伝わる、②エディタが補完や警告を出してくれる、③mypyでバグを事前に発見できるの3つです。小さなスクリプトでは省略しても問題ありませんが、少し規模が大きくなったり、他の人と共有したりするコードでは、型ヒントを付けると保守がぐっと楽になります

主な書き方まとめ

型ヒントの主な書き方をまとめます。

対象 書き方
引数 def f(name: str, age: int = 0):
戻り値 def f(...) -> str:
変数 age: int = 30
リスト / 辞書 list[int] / dict[str, int]
Noneを許す str | None(またはOptional[str]
複数の型 int | str(またはUnion[int, str]

よくある失敗

型ヒントで実行時チェックされると思う

強制されません。違う型を渡しても動きます。検査はmypyで行います。

古いPythonでlist[int]を使う

3.9より前はfrom typing import ListとしてList[int]を使います。

str | NoneをPython 3.9以前で使う

|記法は3.10以降です。古いバージョンはOptional[str]を使います。

Optionalを「省略可能」の意味だと思う

Optional[str]は「strまたはNone」の意味です。引数の省略可否とは別です。

型ヒントを必須だと思う

型ヒントは任意です。付けなくても動きますが、付けると保守性が上がります。

よくある質問

Q型ヒントを書くと型チェックされますか?
Aいいえ。Pythonは型ヒントを実行時にチェックしません。name: strと書いても、数値を渡してエラーにはなりません。型ヒントはあくまで「目印」で、実際の検査はmypyなどの静的解析ツールやエディタが行います。
Qリストや辞書の中身の型はどう書きますか?
Alist[int](intのリスト)、dict[str, int](キーがstr・値がintの辞書)のように書きます。これはPython 3.9以降の書き方です。3.9より前のバージョンではfrom typing import List, DictとしてList[int]Dict[str, int]を使います。
QNoneかもしれない値の型はどう書きますか?
Astr | Noneと書きます(Python 3.10以降)。「文字列またはNone」という意味です。古いバージョンではfrom typing import OptionalとしてOptional[str]を使います。どちらも同じ意味です。
Q型ヒントは書いたほうがよいですか?
A小さなスクリプトでは省略しても問題ありませんが、規模が大きくなったり他の人と共有したりするコードでは、付けると保守性が上がります。コードの意図が明確になり、エディタの補完やmypyによるバグの早期発見にもつながります。
QOptionalは引数を省略できるという意味ですか?
Aいいえ。Optional[str]は「strまたはNone」という型の意味で、引数を省略できるかどうか(デフォルト値の有無)とは別です。引数を省略可能にするにはdef f(x: str = ""):のようにデフォルト値を付けます。

まとめ

  • 引数はname: str、戻り値は-> str、変数はage: int = 30
  • 型ヒントは実行時に強制されません。違う型を渡しても動きます。
  • リスト・辞書はlist[int]dict[str, int](3.9以降)。
  • Noneを許すのはstr | None(3.10以降)またはOptional[str]
  • 型の検査はmypyなどのツールで行います。

型ヒントは、Pythonの動作を変えずに、コードの読みやすさと安全性を高める機能です。「実行時には強制されない(チェックはmypyなどで行う)」という性質さえ理解すれば、混乱なく使えます。まずは関数の引数と戻り値に型を書くところから始めて、エディタの補完が効く快適さを体験してみてください。