如何实现支持多类型参数的类型安全装饰器?(解决TypeVar推断与TypeVarTuple转换问题)
如何实现支持多类型参数的类型安全装饰器?(解决TypeVar推断与TypeVarTuple转换问题)
我完全懂你现在的困扰——单类型的装饰器已经跑通了,但一涉及多类型参数,要么TypeVar直接被推断成object失去类型检查意义,要么用了TypeVarTuple却不知道怎么把传入的类型对象(比如int)映射成函数参数需要的实际类型(比如int类型)。咱们一步步拆解解决这两个问题:
第一个问题:为什么DecoratorNa里的T会变成object?
你写的*type: type[T]这种写法,本质上是让单个TypeVar T去匹配所有传入的类型。当你传入int和str时,类型检查器会找它们的最小公共超类,那自然就是object了——这显然不是你想要的效果。单个TypeVar只能处理同类型的多参数场景,如果你要支持不同类型的多参数,必须用TypeVarTuple来处理可变长度的类型序列。
第二个问题:修正DecoratorNb,实现类型安全的多参数装饰器
核心是搞清楚两个对应关系:
- 装饰器初始化时传入的是类型对象(比如
int是type[int],str是type[str]),所以要把TypeVarTupleTs和这些类型对象对应起来。 - 装饰器要校验的函数参数是实际类型(比如
int、str),所以要把Ts直接展开成函数的参数类型。
直接看修正后的代码:
from collections.abc import Callable from typing import Generic, TypeVar, TypeVarTuple, Unpack # 给C加个bound,让类型检查器更准确地推断实例类型 C = TypeVar("C", bound=object) T = TypeVar("T") Ts = TypeVarTuple("Ts") class Decorator1(Generic[T]): def __init__(self, /, type: type[T]) -> None: super().__init__() def __call__(self, function: Callable[[C, T], None], /) -> Callable[[C, T], None]: return function # 这个DecoratorNa不适合多不同类型场景,建议直接用下面的DecoratorNb方案 # class DecoratorNa(Generic[T]): # def __init__(self, /, *type: type[T]) -> None: # super().__init__() # def __call__(self, function: Callable[[C, T], None], /) -> Callable[[C, T], None]: # return function class DecoratorNb(Generic[*Ts]): # 关键改动:用type[Unpack[Ts]]注解传入的类型对象 # 这样传入的int(type[int])、str(type[str])会被映射到Ts=(int, str) def __init__(self, /, *types: type[Unpack[Ts]]) -> None: super().__init__() self.types = types # 可以存起来供运行时使用 # 这里直接用Unpack[Ts]展开成函数的参数类型 def __call__( self, function: Callable[[C, Unpack[Ts]], None], / ) -> Callable[[C, Unpack[Ts]], None]: return function class MyClass: # 原单类型场景保持不变,正常通过检查 @Decorator1(int) def print_number(self, number: int) -> None: ... @Decorator1(str) def print_string(self, string: str) -> None: ... # 错误场景依然会报错,符合预期 # @Decorator1(int) # def print_another_string(self, string: str) -> None: ... # 用修正后的DecoratorNb,正常通过检查 @DecoratorNb(int, str) def print_number_and_string_b(self, number: int, string: str) -> None: ... # 下面这种参数类型不匹配的情况会被类型检查器捕获,报错 # @DecoratorNb(int, str) # def print_wrong_types(self, number: str, string: int) -> None: ...
关键细节解释
type[Unpack[Ts]]:把Ts这个类型元组展开,每个元素对应传入的类型对象的类型(比如type[int]、type[str]),让类型检查器能准确推断Ts的具体内容。Callable[[C, Unpack[Ts]], None]:把Ts展开成函数的参数类型,这样装饰器就能校验函数的参数是否和初始化时传入的类型完全匹配。- 给
C加bound=object:帮助类型检查器识别C是类实例类型(比如MyClass的self),避免不必要的类型推断错误。
这样修改后,你的多类型装饰器就能像单类型版本一样提供完整的类型安全校验了——正确的参数类型通过检查,错误的会被及时捕获,完全符合你想要的类似PySide6.QtCore.Slot()的效果。
备注:内容来源于stack exchange,提问作者bers
相关产品推荐
相关产品推荐

