Python 3.12でも、型ヒントに新たな機能が追加されました。特に、PEP 695: 型パラメータ文法は、今後のPythonで広く使われることになるでしょう。
PEP 695: 型パラメータ文法¶
Python3.12では、ジェネリックスを使った型ヒントの書き方に大きな変化がありました。
これまで、Pythonの型ヒントはあくまで補助的な機能と位置づけられており、Python3.0で追加された PEP 3107:関数アノテーションで型ヒントが書けるようになってから、基本的には型ヒントのために文法を変更しない方針で拡張されてきました。PEP 464: 型ヒント 以降、いろいろな型ヒントを記述方法が用意されてきましたが、いずれも既存のPython言語の文法で書けるように工夫されています。
しかし、ここで紹介する PEP 695: 型パラメータ文法では、Python3.0以来、十数年ぶりに型ヒント用の文法が大きく追加されることになりました。これからは、型ヒントの機能もPython言語に追加されていくのかも知れません。
従来のジェネリックス¶
Pythonの型ヒントは、ジェネリックス をサポートしていて、色々なデータ型をサポートする、汎用的なクラスや関数を記述できます。
たとえば、次の関数 get_first
はリストから先頭の要素を返します。リストには、整数でも文字列でも、どんな型のデータが入っていてもかまいません。
def get_first(L):
return L[0]
int_list = [0, 1, 2, 3]
first = get_first(int_list) # firstの値は0
この関数の型ヒントは、次のようにかけます。ここでは、「get_first()
の仮引数 L
は、任意の型 T
のオブジェクトを要素とするリストで、戻り値の型は T
です」 と定義しています。
from typing import TypeVar
T = TypeVar('T') # 型変数Tの定義
def get_first(L: list[T])->T:
return L[0]
int_list = [0, 1, 2, 3] # int_listの型は list[int]
first:int = get_first(int_list) # firstの値は0
これまで、Pythonでジェネリックスを定義する場合には、この例のように TypeVar
オブジェクトを作成して型変数を定義する必要がありました。しかし、静的型チェックを行うために、他に専用のオブジェクトを作成するというのは分かりにくく、あまり評判がよくありませんでした。C++などの、ジェネリックスを持つ他のプログラミング言語では、このような独立した定義は必要ありません。
Python3.12以降のジェネリックス¶
そこで、PEP 695: 型パラメータ文法では、TypeVar
の定義なしで、直接ジェネリックスを記述できるようになりました。この修正は、すでに Pyright で利用可能になっています。
Python3.12では、上記の get_first()
を次のように書けます。
def get_first[T](L: list[T])->T:
return L[0]
int_list = [0, 1, 2, 3] # int_listの型は list[int]
first:int = get_first(int_list) # firstの値は0
型変数は、def get_first[T](...
のように、関数名に続けて []
の中に記述します。複数の型変数を要する場合は、次のように記述します。
def make_tuple[T1, T2](a: T1, b: T2)->tuple[T1, T2]:
return (a, b)
tp = make_tuple(10, "hello") # tpの型は tuple[int, str]
ジェネリッククラス¶
また、これまでジェネリックスを使った汎用的なクラスは、次のように Generic
型を基底クラスとして定義していました。
from typing import TypeVar, Generic
T = TypeVar('T') # 型変数Tの定義
class Stack(Generic[T]):
_list: list[T]
def __init__(self):
self._list = []
def push(self, v:T)->None:
self._list.append(v)
def pop(self)->T:
return self._list.pop()
stack = Stack[int]()
stack.push(100)
print(stack.pop())
これも、Python3.12では TypeVar
と Generic
を使わずに、次のように書けるようになりました。
class Stack[T]:
_list: list[T]
def __init__(self):
self._list = []
def push(self, v:T)->None:
self._list.append(v)
def pop(self)->T:
return self._list.pop()
新しい記法を利用した場合、基底クラスに Generic
がなくとも、自動的に Generic
の派生型となります。