ジェネリクスのおさらいと型変数タプル
Python 3.11では、tuple[*Ts]というような型ヒントにより、可変長の引数にジェネリクスの利用が可能になりました。これは型変数タプルTypeVarTupleで実現されます。
ジェネリクスとは?
型変数タプルを紹介するにあたり、まずはジェネリクスについておさらいしておきます。ジェネリクスは、JavaやC#などのプログラミング言語でおなじみの機能ですが、Pythonでは型変数TypeVarを使った型ヒントをジェネリクスと呼びます。ジェネリクスを使うことで、特定の型を明示的に指定するのではなく、実際の引数に応じて変化する汎用的な型ヒントを指定できます。例えば以下のリストは、任意の型の引数xから2要素のタプルを生成して返す関数make_tupleを定義しています。
from typing import TypeVar
# ジェネリックな型変数Tを定義
T = TypeVar("T") (1)
# x: Tをタプルにして返す関数
def make_tuple(x: T) -> tuple[T, T]:
return (x, x)
value_int = make_tuple(100) (2)
value_str = make_tuple('Hello')
print(value_int, value_str) # 実行結果:(100, 100) ('Hello', 'Hello')
(1)で型ヒントに与えられているTは、TypeVarによる型変数です。(2)の関数呼び出しではxの型(intおよびstr)がTに入ります。これにより、戻り値であるタプルの各要素の型もintあるいはstrになります。Pythonでは、このようにして関数の汎用化であるジェネリクスが実現されています。
型変数タプル
しかしながら、型変数には1個の型しか代入できないので、例えば複数の異なる型からなるタプルを引数として渡す関数をジェネリクスで定義することはできません。そこでPython 3.11では、型変数タプルTypeVarTupleが導入されました。TypeVarTupleを使うと、複数の型を持てる型変数を定義できるので、これにより以下のリストのような関数定義が可能になります。
from typing import TypeVarTuple
# 型変数タプルを定義
Ts = TypeVarTuple("Ts") (1)
# タプルに長さを加えて返す関数
def add_tuple_count(x: tuple[*Ts]) -> tuple[*Ts, int]: (2)
return (*x, len(x))
value = add_tuple_count(('Hello', 100))
print(value) # 実行結果:('Hello', 100, 2)
(1)のTsは、TypeVarTupleによって複数の型を収納できる型変数です。(2)のように、関数の引数は*Tsによる型ヒントとなっており、複数の型によるタプルを受け入れることになります。戻り値も同様の型ヒントとなり、末尾は必ずintになります。
静的型チェックツールの実行結果
このリストはPylanceによるチェックでは問題になりませんが、mypyによるチェックで「"TypeVarTuple" support is experimental」「Invalid type comment or annotation」などのエラーになります。本稿作成時点のmypyでは型変数タプルのサポートが完全でないためと思われます。--enable-incomplete-feature=TypeVarTupleオプションの指定でもエラーは解消されない点に注意してください。
定義されているクラスを表す型変数Selfが使用可能に
Python 3.11では、クラスの関数の戻り値が自分自身であることを示す型変数Selfが利用可能になりました。Python 3.10までは、以下のようにtyping.TypeVarを使って疑似的にSelf型を実現していたので、非常にシンプルになりました。
from typing import TypeVar
# 上界にMyClassを指定した型変数Selfを宣言
Self = TypeVar("Self", bound="MyClass") (1)
class MyClass:
x: int
# 自身を返すreturn_myself関数
def return_myself(self: Self) -> Self: (2)
return self
c = MyClass()
c.return_myself().x = 1 # Selfを介してフィールドに値を設定
print(c.x) # 実行結果:1
(1)のTypeVarにより、SelfはMyClassのサブクラスという型変数になります。(2)では、MyClassクラスにおいて、return_myselfメソッドは自分自身を受け取って自分自身を返す関数として定義されています。TypeVarから型変数を生成する必要があるなどやや冗長ということで、Python 3.11では以下のように記述することができるようになりました。
from typing_extensions import Self (1)
# 型変数の宣言は不要
class MyClass:
x: int
def return_myself(self: Self) -> Self:
return self
クラスと関数の定義はそのままで、(1)のようにSelfのためのimport文を変更します。TypeVarによる型変数の定義がなくなることで、だいぶスッキリしますね。
