変数が「整数か、もしくは None かもしれない」といった状況は実務で頻繁に現れる。こうした「複数の型のどれかを取りうる」ケースを表現するのが Union であり、None を含む特殊なケースを簡潔に書くのが Optional だ。
Union の基本
Union は「この変数はいくつかの型のうちどれかを取る」と宣言するための仕組みである。
from typing import Union value: Union[int, str] = 42 value = "hello" # これも OK
Union[int, str] と書けば、int でも str でも受け入れられる。Python 3.10 以降ではパイプ演算子で同じ意味を表現できる。
value: int | str = 42 value = "hello"
Union[int, str] — typing モジュールのインポートが必要。3.9 以前でも使える。
int | str — シンプルで読みやすい。Python 3.10 以降で利用可能。
パイプ記法のほうが直感的なので、3.10 以降を前提にできるなら積極的に使うとよい。
Optional とは何か
Optional は「その型か、None のどちらかを取る」ことを意味する。実態としては Union の省略形にすぎない。
from typing import Optional name: Optional[str] = "Alice" name = None # これも OK
Optional[str] は Union[str, None] と完全に等価だ。Python 3.10 以降なら str | None と書ける。
name: str | None = "Alice" name = None
どの書き方を選ぶかはプロジェクトの Python バージョンとチームの方針次第だが、意味はすべて同じである。
関数の引数と戻り値での使い方
Union や Optional が最も活躍するのは関数のシグネチャだ。
def find_user(user_id: int) -> str | None: users = {1: "Alice", 2: "Bob"} return users.get(user_id)
この関数は、ユーザーが見つかれば文字列を返し、見つからなければ None を返す。戻り値の型に str | None と書くことで、呼び出し側に「None チェックが必要だ」と伝えられる。
デフォルト値が None の引数にも Optional は自然にはまる。
def greet(name: str, title: str | None = None) -> str: if title: return f"{title} {name}" return f"Hello, {name}"
None チェックと型の絞り込み
Optional な変数をそのまま使おうとすると、型チェッカに怒られることがある。
def get_length(text: str | None) -> int: return len(text) # エラー: text が None の可能性がある
None の可能性を排除してから使う必要がある。
def get_length(text: str | None) -> int: if text is None: return 0 return len(text) # ここでは text は str と確定
if text is None のチェックを通過した後のブロックでは、型チェッカが自動的に text を str と推論してくれる。これを型の絞り込み(narrowing)と呼ぶ。
Union を使いすぎない
Union は便利だが、3 つ以上の型を並べ始めたら設計を見直すサインかもしれない。
# あまり良くない例 result: int | str | list[int] | None = some_function()
こうなると呼び出し側で分岐が増えすぎて扱いにくい。戻り値の型が複雑になる場合は、データクラスや例外を使って整理するほうが保守しやすくなる。
Union に含める型は 2〜3 個が目安。それ以上になったら設計を再考する。
Optional は Union の特殊ケース。「値がないかもしれない」を表すときに使う。
型ヒントは「何が来るか」を明示するためのものだ。Union を乱用して「何でも来る」状態にしてしまっては本末転倒になる。適切な粒度で型を絞り込むことが、読みやすく安全なコードにつながる。