Python Unionとは?複数の型を許容する型ヒントの書き方

PythonのUnionは、「複数の型のうちどれか」を表す型ヒントです。

「数値でも文字列でも受け取れる関数」のように、入力の型が1つに定まらない場面で使います。


Unionの基本的な使い方

from typing import Union

def to_int(v: Union[int, str]) -> int:
    return int(v)

print(to_int(5))
print(to_int("5"))

実行結果:

5
5

Union[int, str]は「intかstr」という意味です。3つ以上も並べられます。

Union[int, float, str]

想定外の型を弾いてくれる

# a4.py
from typing import Union

def to_int(v: Union[int, str]) -> int:
    return int(v)

to_int(1.5)

実行自体は通ってしまいます(int(1.5)は1)。しかし型チェッカーは指摘します。

$ mypy a4.py
a4.py:6: error: Argument 1 to "to_int" has incompatible type "float"; expected "Union[int, str]"  [arg-type]

「floatは想定していない」と分かりました。小数を渡すと切り捨てられるため、意図しない結果になる箇所を事前に見つけられます。


Python 3.10以降は|が書ける

def to_int(v: int | str) -> int:
    return int(v)

importが不要で、見た目も直感的です。3.10以降を対象にするなら、こちらが推奨されています。

Python 3.9で使うとエラーになります。

x = int | str

実行結果(Python 3.9):

TypeError: unsupported operand type(s) for |: 'type' and 'type'
対象 書き方
3.9以前も含む Union[int, str]
3.10以降のみ int | str

使う側では型を絞り込む

Unionを受け取った関数の中で、片方にしかないメソッドを呼ぶと怒られます。

def show(v: Union[int, str]) -> None:
    print(v.upper())          # strにしかない
error: Item "int" of "Union[int, str]" has no attribute "upper"

isinstance()で分岐すれば、型チェッカーが各分岐の中での型を理解します。

def show(v: Union[int, str]) -> None:
    if isinstance(v, str):
        print(v.upper())      # ここではstr
    else:
        print(v + 1)          # ここではint

これを型の絞り込み(narrowing)と呼びます。Unionを使うときは、ほぼ必ずisinstance()とセットになります。


Optionalとの関係

from typing import Optional, Union

print(Optional[int] == Union[int, None])

実行結果:

True

Optional[X]はUnion[X, None]の短縮形です。Noneとの組み合わせならOptionalのほうが意図が明確なので、そちらを使ってください。

詳しくは「Python Optionalとは?Noneを許容する型ヒントの書き方」で解説しています。


Unionを使いすぎない

便利な反面、設計の甘さを覆い隠す道具でもあります。

def process(data: Union[str, list, dict, int, None]) -> Union[str, list, None]:
    ...

これは「何が来るか分からない」と言っているのと同じで、型ヒントの意味がほとんどありません。使う側は毎回全パターンを調べる必要があります。

Unionが適切 見直すべき
int | str(数値の文字列表現を許す) 4つ以上並ぶ
str | Path(パスの受け取り) 戻り値がUnion
X | None(=Optional) 分岐ごとに処理が全く違う

特に戻り値のUnionは扱いにくいです。呼び出し側に分岐を強制するためで、関数を分けるほうが素直なことがほとんどです。

def get_text(uid: int) -> str: ...
def get_rows(uid: int) -> list[str]: ...

数値はUnionを書かなくてよいことがある

def area(w: float, h: float) -> float:
    return w * h

print(area(3, 4))

実行結果:

12

intを渡していますが、型チェッカーは通します。floatと書けばintも受け付けるという特別扱いがあるためです。

Union[int, float]と書く必要はありません。floatだけで十分です。


Unionのまとめ

  • Union[X, Y]は「XかY」
  • 3.10以降はX | Yと書ける(3.9ではTypeError)
  • 使う側ではisinstance()で絞り込む
  • Optional[X]はUnion[X, None]の短縮形
  • 並べすぎると型ヒントの意味が消える。4つ以上は設計を見直す
  • 戻り値のUnionは避け、関数を分ける
  • 数値はfloatと書けばintも通る

あわせて読みたい

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら