Pythonの型ヒントUnionとTypeVarの違いと使い方

PythonのUnionとTypeVarの違いについて解説します。

Pythonの型ヒントUnionとTypeVarの違いと使い方

Pythonの型ヒントは、Pythonの静的型付けのサポートを強化するために導入されました。型ヒントを使用することで、関数やメソッドがどのような型の引数を期待し、どのような型の戻り値を返すかを明示的に示すことができます。

型ヒントには、UnionTypeVarという2つの重要な機能があり、それぞれ似たような使い方ができるため、基本的な使い方とそれぞれの違いを解説します。

なお、Pythonにおける型ヒントはあくまでアノテーションであり、コンパイル時や実行時に型チェックを行なってエラーを出してくれるようなものではありません。以下の解説における『エラー』とは、Pylanceなどの静的型付けチェックツールにおけるエディタ上のエラーを指します。

Unionの基本的な使い方

まず、Unionは、指定された変数の型が、Union内で指定した複数の型のうちの1つであることを示します。例えば、次のような関数があったとします。

def get_length(x):
    return len(x)

この関数は、引数xの長さを返す関数ですが、xが文字列またはリストの場合にだけ機能し、たとえばint型などではエラーになります。そこで、引数xの型をUnionを使って次のように定義することができます。

from typing import Union

# 型ヒントで、引数はstr型かlist型、戻り値はint型であることを示す
def get_length(x: Union[str, list]) -> int:
    return len(x)

ここで、Union[str, list]は、xstrまたはlistのいずれかであることを示しています。このように、Unionを使用することで、関数やメソッドが受け入れることができる複数の型を指定することができます。

TypeVarの基本的な使い方

次に、TypeVarは、ジェネリック関数やクラスで使用される型パラメーターを定義するために使用されます。例えば、次のようなジェネリック関数があったとします。

from typing import TypeVar, List

T = TypeVar('T')

# List型の変数を引数にとり、その要素の型を戻り値の型に指定している
def get_first_element(x: List[T]) -> T:
    return x[0]

この関数は、リストの最初の要素を返す関数ですが、リストの要素の型が何であるかは、引数に実際のリストが渡されるまでわかりません。そのため、リストの要素の型を取り出して、それを戻り値として使うことを示すためにTypeVarを使用しています。

また、TypeVarは指定できる型を制限することもできます。たとえば、T = TypeVar('T', int, str)のような書き方で、Tに指定する型をint型とstr型に制限できます。

from typing import TypeVar

T = TypeVar('T', int, str)

# int型かstr型の引数と戻り値を指定
def multiply_by_two(x: T) -> T:
    return x * 2

UnionとTypeVarの違い

上記の例を見ると、Unionは、複数の型を組み合わせた場合に使用され、TypeVarは、型パラメーターを定義する場合に使用されることがわかります。

両方とも、引数や戻り値の型を、複数の種類に制限することができますが、Unionはあくまで型の集合、TypeVarは複数のパターンがある型変数であるという点で異なります。

たとえば、Union[int, str]int型かstr型を許容する複合的な型の集合であり、たとえ指定した引数や戻り値がどちらの型であるかがわかっているとしても、型チェックの時点では複合的な型であると判断されます。そのため、以下のような例では、戻り値でint型のメソッドを利用することができません。

from typing import Union

def multiply_by_two(x: Union[int, str]) -> [int, str]:
    return x * 2


num: int = multiply_by_two(10) # Pylanceなどでエラーになる

num: Union[int, str] = multiply_by_two(10) # この書き方ならOKだが、int型ではなくなる
num.to_bytes(2, byteorder='big') # そのため、int型のメソッドを扱うとPylanceなどでエラーが出る

一方、TypeVar('T', int, str)は、int型かstr型を許容する型変数Tを宣言しており、関数の使用時に型が確定したのであれば、その引数や戻り値はその確定した型として扱うことができます。

from typing import TypeVar

T = TypeVar('T', int, str)

def multiply_by_two(x: T) -> T:
    return x * 2


num: int = multiply_by_two(10) # エラーは出ず、int型として扱うことができる
num.to_bytes(2, byteorder='big') # int型のメソッドも問題なく扱うことができる