Pythonの型ヒント、UnionとTypeVarの違いを理解する
こんにちは。サービスグループの武田です。
Pythonは動的型付きのプログラミング言語ですが、3.5から型ヒント(Type Hints)が導入されました。
型ヒントは静的型付き言語のように型によるチェックができるようになる一方で、実行時には影響を及ぼしません。つまり型ヒントはアノテーションです。型ヒントのチェックはコンパイラなどが言語として提供されているわけではありません。代わりにmypyを使うのがデファクトのようです。
このエントリでは型ヒントに含まれているUnion
とTypeVar
の違いを確認してみました。
環境
次のような環境で検証をしています。
$ python3 -V Python 3.8.3 $ mypy -V mypy 0.782
Union vs TypeVar
Union
は「str or int」のように「いくつかのある型のうちいずれか」にマッチすればOKという型ヒントです。TypeVar
はジェネリクスの型変数を定義するための型ヒントです。こう書くと、そもそも比較するような二者ではないのですが、「似たような使い方」ができてしまうため整理しました。
というわけで次のコードを見てください。
from typing import TypeVar, Union U = Union[str, int] T = TypeVar("T", str, int) def test_union(p: U): pass def test_typevar(p: T): pass test_union(1) # OK test_union("union") # OK test_union(None) # NG test_typevar(2) # OK test_typevar("typevar") # OK test_typevar(None) # NG
チェック結果は次のようになります。
$ mypy main.py main.py:18: error: Argument 1 to "test_union" has incompatible type "None"; expected "Union[str, int]" main.py:22: error: Value of type variable "T" of "test_typevar" cannot be "None" Found 2 errors in 1 file (checked 1 source file)
それぞれstr型かint型の引数を取る関数を宣言しており、実際のチェックもそのように動いています。これだけ見ると両者に違いが見られません(エラーメッセージは異なりますが)。はっきりとした違いを確認するため、次のようにコードを変更してみます。
from typing import TypeVar, Union U = Union[str, int] T = TypeVar("T", str, int) def test_union(p1: U, p2: U): pass def test_typevar(p1: T, p2: T): pass test_union(1, 2) # OK test_union("union", "onion") # OK test_union(1, "onion") # OK test_typevar(1, 2) # OK test_typevar("typevar", "foobar") # OK test_typevar(1, "foobar") # NG
チェック結果は次のようになります。
$ mypy main.py main.py:23: error: Value of type variable "T" of "test_typevar" cannot be "object" Found 1 error in 1 file (checked 1 source file)
test_unionはエラーになっていません。引数のp1とp2はそれぞれ型ヒントとしてU
を指定していますが、それぞれの引数について「str or int」を確認しています。つまりp1とp2の型が同じか異なるのかは関係ないのです。一方のtest_typevarは3つ目がエラーになっています。引数のp1とp2に型ヒントとしてT
を指定していますが、先ほどと違い型変数T
が指定されたパラメーターは すべて同じ型 である必要があります *1。エラーが出た関数呼び出しは、型変数T
が指定されているパラメーターに異なる型の値を与えたためエラーになったというわけです。
まとめると、U = Union[str, int]
は「str型かint型のいずれかを許可する複合型」を宣言しています。またT = TypeVar("T", str, int)
は「str型かint型に制限した型変数T」を宣言しています。
まとめ
Unionは複合型でTypeVarは型変数、がまずは基本です(そもそもTypeVarのベーシックな使い方はT = TypeVar("T")
)。「A or Bの型」の実現方法から入ってしまったため、少し混乱しました。Pythonでも型宣言していきましょう!
脚注
- TypeVarはデフォルトが不変です。共変、反変、境界の指定もできるため厳密には常に同じ型とは言えませんが、このケースでは同じ型であることが求められます ↩