Python中如何修改带参装饰器支持任意数量泛型序列并保留类型提示?
实现支持任意泛型参数序列的装饰器并保留类型提示
要让deco_with_params支持任意数量的泛型类型序列,同时让mypy能正确校验类型匹配,核心是利用PEP 646的TypeVarTuple和Unpack处理可变长度的类型参数,结合泛型类实现装饰器。
完整实现代码
from collections.abc import Callable, Sequence from typing import TypeVarTuple, Unpack, Generic # 定义可变长度的类型元组,绑定被装饰函数的参数类型序列 Ts = TypeVarTuple('Ts') class Decorator(Generic[Unpack[Ts]]): def __init__(self, **kwargs: Sequence[Unpack[Ts]]): self.param_sequences = kwargs def __call__(self, func: Callable[[Unpack[Ts]], None]) -> None: # 按函数参数顺序提取对应序列的第一个元素(对应原代码的shortcut逻辑) args = [ self.param_sequences[param_name][0] for param_name in func.__code__.co_varnames[:func.__code__.co_argcount] ] func(*args) # 导出装饰器 deco_with_params = Decorator
关键逻辑说明
- 可变类型绑定:
TypeVarTuple('Ts')定义了可变长度的类型集合,用来指代被装饰函数的所有参数类型;Generic[Unpack[Ts]]让装饰器类与这个类型集合绑定。 - 参数类型约束:
**kwargs: Sequence[Unpack[Ts]]要求装饰器的每个关键字参数必须是对应类型的序列,序列元素类型与Ts中的类型一一对应。 - 类型校验匹配:
__call__方法只接收参数类型为Unpack[Ts]的函数,确保函数参数类型与装饰器参数的序列元素类型完全匹配。 - 参数传递逻辑:通过
func.__code__.co_varnames获取函数的参数名称,按顺序从装饰器的参数序列中取第一个元素传递给函数,保持原代码的shortcut行为。
用法验证
合法用法(mypy接受)
@deco_with_params(string=["one", "two", "three"], number=[4., 5.1, 6.7], extra=[True, False]) def foo(string: str, number: float, extra: bool) -> None: print(string, number, extra)
非法用法(mypy拒绝)
@deco_with_params(string=["one", "two", "three"], number=[True, False], extra=[4., 5.1, 6.7]) def bar(string: str, number: float, extra: bool) -> None: print(string, number, extra)
mypy会提示number参数的序列元素类型bool与函数参数类型float不匹配,extra参数的序列元素类型float与函数参数类型bool不匹配,符合预期校验逻辑。
内容的提问来源于stack exchange,提问作者frans
相关产品推荐
相关产品推荐

