You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Python中作为可调用对象的装饰器添加类型注解并实现全阶段类型校验

解决可调用装饰器的类型注解问题(兼顾装饰与调用阶段的类型校验)

你的问题核心在于实例方法在未绑定(类上的方法)和绑定(实例上的方法)状态下的类型差异,之前的方案要么无法适配这种差异导致mypy报错,要么需要重复定义参数导致冗余。下面是一种更简洁且类型安全的解决方案,利用ParamSpec和泛型Protocol来避免重复定义,同时保证装饰和调用阶段的类型校验都能正常工作。


完整解决方案代码

from typing import Any, Awaitable, Callable, ParamSpec, Protocol, TypeVar

# 定义泛型变量:Self表示实例类型,P捕获方法的参数(除self外),R限制返回值为Awaitable[str]
Self = TypeVar('Self')
P = ParamSpec('P')
R = TypeVar('R', bound=Awaitable[str])

# 定义Protocol,同时描述未绑定方法(带self)和绑定后的方法(通过__get__)
class UnboundMethod(Protocol[Self, P, R]):
    def __call__(self, self_arg: Self, *args: P.args, **kwargs: P.kwargs) -> R: ...
    def __get__(self, instance: Self | None, owner: type[Self]) -> Callable[P, R] | Self: ...

def some_decorator() -> Callable[[UnboundMethod[Self, P, R]], UnboundMethod[Self, P, R]]:
    def wrapper(func: UnboundMethod[Self, P, R]) -> UnboundMethod[Self, P, R]:
        print("Decorator is applied.")
        return func
    return wrapper

class MyClass:
    @some_decorator()
    async def my_method(self, some_arg: int) -> str:
        return f"Result: {some_arg}"

# 正确调用示例
if __name__ == "__main__":
    obj = MyClass()
    await obj.my_method(some_arg=1)  # 类型正确,mypy无报错

方案说明

  1. 泛型变量的作用:

    • Self:用来表示类的实例类型,让Protocol可以适配任意类的实例方法
    • P(ParamSpec):捕获方法除self外的所有参数,避免重复写参数列表,保证一致性
    • R:限制方法返回值必须是Awaitable[str],符合你的需求
  2. Protocol的设计:

    • __call__方法描述未绑定的实例方法(类上的方法,需要传入self作为第一个参数)
    • __get__方法模拟Python的方法绑定机制:当从实例获取方法时,返回一个绑定后的Callable[P, R](不需要再传self),这样mypy就能正确识别实例调用时的参数类型

验证你的错误场景

场景1:调用时传入错误类型参数

obj = MyClass()
obj.my_method("str")  # mypy报错:Argument 1 has incompatible type "str"; expected "int"

场景2:定义方法时指定错误参数类型

class BadMyClass:
    @some_decorator()
    async def my_method(self, some_arg: str) -> str:  # mypy报错:Argument 2 of "__call__" has incompatible type "str"; expected "int"
        return f"Result: {some_arg}"

这两种情况mypy都会给出准确的类型错误提示,完全符合你的需求。


为什么之前的方案有问题?

  • 第一个方案的MethodCallable只描述了绑定后的方法类型,而装饰器接收的是未绑定的实例方法(带self参数),类型不匹配导致mypy报错
  • 第二个方案手动重复定义了__call__和__get__的参数,不仅冗余,还容易出现参数不一致的问题,导致类型校验失效

这个方案通过泛型和ParamSpec彻底解决了这些问题,同时保持代码简洁且类型安全。

内容的提问来源于stack exchange,提问作者Pablo Estevez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 09:12:30