Concatenate は ParamSpec と組み合わせて、関数の引数に新しいパラメータを追加する型を表現します。
基本的な使い方
from typing import Callable, Concatenate, ParamSpec, TypeVar
P = ParamSpec("P")
T = TypeVar("T")
def with_context(
func: Callable[Concatenate[str, P], T]
) -> Callable[P, T]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
context = "default_context"
return func(context, *args, **kwargs)
return wrapper
Concatenate[str, P] は「最初に str 型の引数があり、その後に P の引数が続く」という意味です。
実用例:認証付きデコレータ
from typing import Callable, Concatenate, ParamSpec, TypeVar
P = ParamSpec("P")
T = TypeVar("T")
class User:
def __init__(self, name: str):
self.name = name
def authenticated(
func: Callable[Concatenate[User, P], T]
) -> Callable[P, T]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
user = User("current_user") # 実際は認証処理
return func(user, *args, **kwargs)
return wrapper
@authenticated
def get_profile(user: User, detailed: bool = False) -> dict:
return {"name": user.name, "detailed": detailed}
# 呼び出し時は user を渡さない
result = get_profile(detailed=True)
メソッドに self を追加
from typing import Callable, Concatenate, ParamSpec, TypeVar
P = ParamSpec("P")
T = TypeVar("T")
S = TypeVar("S")
def bind_self(
func: Callable[Concatenate[S, P], T]
) -> Callable[P, T]:
...
Concatenate の位置
Concatenate の追加パラメータは常に先頭に来ます。
# 正しい
Concatenate[int, str, P] # int, str, その後に P の引数
# 不正(Concatenate の後に他のパラメータは置けない)
# Concatenate[P, int] # エラー
Concatenate を使うと、デコレータが引数を追加・削除する場合でも、正確な型情報を維持できます。型チェッカーが呼び出しの正当性を検証できるため、バグの早期発見に役立ちます。