Python类型提示:适配函数与类方法的装饰器参数转换问题
问题:装饰器兼容普通函数与类方法的类型检查错误
我需要实现一个装饰器,根据目标是普通函数还是类方法,分别转换第一个或第二个参数(将Given类型转为Desired类型)后再调用原函数。核心代码本身在mypy和pyright中无报错,但实际用该装饰器装饰函数和类方法时,两款类型检查工具均抛出类型不兼容错误。我无法理解为何联合类型无法匹配正确的调用签名,也不清楚为何类型变量T未被正确推断为None而非Any,求解决方案。
核心代码示例
from typing import Callable, Concatenate, ParamSpec, TypeVar, overload, Union, cast from inspect import ismethod T = TypeVar("T") P = ParamSpec("P") class FixedClass: ... class Given: ... class Desired(Given): def __init__(self): super().__init__() Function = Callable[Concatenate[Desired, P], T] TransformedFunction = Callable[Concatenate[Given, P], T] FunctionTransformer = Callable[[Function[P, T]], TransformedFunction[P, T]] Method = Callable[Concatenate[FixedClass, Desired, P], T] TransformedMethod = Callable[Concatenate[FixedClass, Given, P], T] MethodTransformer = Callable[[Method[P, T]], TransformedMethod[P, T]] def convert_oracle(given: Given) -> Desired: return Desired() def transform() -> Union[FunctionTransformer[P, T], MethodTransformer[P, T]]: @overload def command(func: Function[P, T]) -> TransformedFunction[P, T]: ... @overload def command(func: Method[P, T]) -> TransformedMethod[P, T]: ... def command(func: Union[Function[P, T], Method[P, T]]) -> Union[TransformedMethod[P, T], TransformedFunction[P, T]]: if ismethod(func): def transformed_method(self: FixedClass, given: Given, *args: P.args, **kwargs: P.kwargs) -> T: desired: Desired = convert_oracle(given) method = cast(Method[P, T], func) return method(self, desired, *args, **kwargs) return transformed_method def transformed_function(given: Given, *args: P.args, **kwargs: P.kwargs) -> T : desired: Desired = convert_oracle(given) function = cast(Function[P, T], func) return function(desired, *args, **kwargs) return transformed_function return command
使用示例
from typing import Any from main import transform, FixedClass, Desired @transform() def write_command(desired: Desired) -> None: return None class Test(FixedClass): @transform() def read_command(self, desired: Desired) -> None: return None
pyright报错信息
/home/runner/MyPy-Test/test.py:6:2 - error: Argument of type "(desired: Desired) -> None" cannot be assigned to parameter of type "(FixedClass, Desired, ...) -> Unknown" Type "(desired: Desired) -> None" cannot be assigned to type "(FixedClass, Desired, ...) -> Unknown" Function accepts too many positional parameters; expected 1 but received 2 Parameter 1: type "FixedClass" cannot be assigned to type "Desired" "FixedClass" is incompatible with "Desired" (reportGeneralTypeIssues)
/home/runner/MyPy-Test/test.py:12:4 - error: Argument of type "(self: Self@Test, desired: Desired) -> None" cannot be assigned to parameter of type "(Desired, ...) -> Unknown" Type "(self: Self@Test, desired: Desired) -> None" cannot be assigned to type "(Desired, ...) -> Unknown" Parameter 1: type "Desired" cannot be assigned to type "Self@Test" "Desired" is incompatible with "Test" (reportGeneralTypeIssues)
/home/runner/MyPy-Test/test.py:12:4 - error: Argument of type "(self: Self@Test, desired: Desired) -> None" cannot be assigned to parameter of type "(FixedClass, Desired, ...) -> Unknown" Type "(self: Self@Test, desired: Desired) -> None" cannot be assigned to type "(FixedClass, Desired, ...) -> Unknown" Parameter 1: type "FixedClass" cannot be assigned to type "Self@Test" "FixedClass" is incompatible with "Test" (reportGeneralTypeIssues)
mypy报错信息
test.py:6: error: Argument 1 has incompatible type "Callable[[Desired], None]"; expected "Callable[[Desired, VarArg(Any), KwArg(Any)], Any]" [arg-type] test.py:6: note: This is likely because "write_command" has named arguments: "desired". Consider marking them positional-only
test.py:6: error: Argument 1 has incompatible type "Callable[[Desired], None]"; expected "Callable[[FixedClass, Desired, VarArg(Any), KwArg(Any)], Any]" [arg-type]
test.py:12: error: Argument 1 has incompatible type "Callable[[Test, Desired], None]"; expected "Callable[[Desired, VarArg(Any), KwArg(Any)], Any]" [arg-type] test.py:12: note: This is likely because "read_command of Test" has named arguments: "self". Consider marking them positional-only
test.py:12: error: Argument 1 has incompatible type "Callable[[Test, Desired], None]"; expected "Callable[[FixedClass, Desired, VarArg(Any), KwArg(Any)], Any]" [arg-type] test.py:12: note: This is likely because "read_command of Test" has named arguments: "self", "desired". Consider marking them positional-only Found 4 error
解决方案
问题根源在于原代码的类型设计让类型检查器无法正确区分函数和方法的重载分支,而且返回联合类型的转换器会导致类型推断混乱。以下是修正后的实现:
修改后的核心代码
from typing import Callable, Concatenate, ParamSpec, TypeVar, overload from inspect import isfunction # 定义类型变量:返回值、参数列表、类方法的self类型(绑定FixedClass子类) T = TypeVar("T") P = ParamSpec("P") SelfT = TypeVar("SelfT", bound="FixedClass") class FixedClass: ... class Given: ... class Desired(Given): def __init__(self): super().__init__() def convert_oracle(given: Given) -> Desired: return Desired() # 重载装饰器,分别处理普通函数和类方法 @overload def transform(func: Callable[Concatenate[Desired, P], T]) -> Callable[Concatenate[Given, P], T]: ... @overload def transform(func: Callable[Concatenate[SelfT, Desired, P], T]) -> Callable[Concatenate[SelfT, Given, P], T]: ... def transform(func): # 运行时判断是普通函数还是类方法(类方法在未绑定实例时是function,绑定后是method,这里处理未绑定的情况) if isfunction(func): # 普通函数:转换第一个参数 def wrapper(given: Given, *args: P.args, **kwargs: P.kwargs) -> T: desired = convert_oracle(given) return func(desired, *args, **kwargs) return wrapper else: # 类方法:转换第二个参数 def wrapper(self: SelfT, given: Given, *args: P.args, **kwargs: P.kwargs) -> T: desired = convert_oracle(given) return func(self, desired, *args, **kwargs) return wrapper
关键修正点
- 重载装饰器本身:不再返回联合类型的转换器,而是直接对
transform函数做重载,让类型检查器直接匹配对应的函数/方法签名。 - 支持子类self类型:新增
SelfT类型变量并绑定FixedClass,确保子类的self类型(如Test)能被正确推断,解决原代码中FixedClass与Test不兼容的问题。 - 简化运行时判断:用
isfunction判断普通函数,类方法在定义阶段是函数对象,绑定实例后才是方法,这样能正确区分两种情况。 - 移除不必要的类型别名:直接在重载中写出签名,避免类型别名导致的推断混乱。
验证修改后的使用示例
原使用示例无需修改,此时mypy和pyright均不会再抛出类型错误,且类型变量T会被正确推断为None。
内容的提问来源于stack exchange,提问作者YousefZ
相关产品推荐
相关产品推荐

