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

如何为Python装饰器编写适配类方法实例调用的泛型类型提示?

解决类方法使用装饰器时的mypy类型检查错误

问题分析

你编写的装饰器支持普通函数的直接调用或线程执行,但应用于类方法时,mypy会报参数类型不匹配错误。核心原因是:装饰器的ParamSpec(P)包含了方法的self参数,当方法绑定到实例后,self已被隐式传入,但mypy仍认为start_thread需要显式传入self,导致参数类型校验失败。

修改后的装饰器代码

通过拆分绑定/未绑定方法的类型处理,实现符合mypy预期的类型提示:

from typing import Callable, Generic, TypeVar, ParamSpec
from functools import wraps
from threading import Thread

P = ParamSpec('P')
R = TypeVar('R')
InstanceT = TypeVar('InstanceT')

class CallOrThreadFunction(Generic[InstanceT, P, R]):
    def __init__(self,
                 start_thread: Callable[P, Thread],
                 call: Callable[P, R]):
        self.start_thread = start_thread
        self.call = call
        
    def __get__(self, instance: InstanceT, owner: type[InstanceT] | None = None) -> 'BoundCallOrThreadFunction[P, R]':
        # 绑定实例时返回适配后的类型,自动移除self参数
        bound_call = self.call.__get__(instance, owner)
        bound_start_thread = self.start_thread.__get__(instance, owner)
        return BoundCallOrThreadFunction(bound_start_thread, bound_call)

class BoundCallOrThreadFunction(Generic[P, R]):
    def __init__(self,
                 start_thread: Callable[P, Thread],
                 call: Callable[P, R]):
        self.start_thread = start_thread
        self.call = call

def call_or_start_thread(function: Callable[P, R]) -> CallOrThreadFunction[object, P, R]:
    @wraps(function)
    def start_thread(*args: P.args, **kwargs: P.kwargs) -> Thread:
        thread = Thread(target=function, args=args, kwargs=kwargs, daemon=True)
        thread.start()
        return thread

    return CallOrThreadFunction(start_thread, function)

关键修改点

  1. 新增类型变量与类拆分:
    • 引入InstanceT表示类实例类型,区分未绑定方法(类层面)和已绑定方法(实例层面)的类型。
    • 拆分出BoundCallOrThreadFunction专门处理实例绑定后的方法类型,此时参数列表自动移除self。
  2. 实现__get__方法:
    当实例访问装饰后的方法时,通过__get__返回绑定后的类型,让mypy正确识别参数列表(不再要求传入self)。
  3. 修正线程返回值:
    原代码错误返回Thread类,现在改为返回创建的Thread实例,符合实际逻辑和类型规范。

使用示例(mypy可通过校验)

class Employee:
    def __init__(self, name: str):
        self.name = name

    @call_or_start_thread
    def my_method(self, parameter: int = 1):
        print(f'name: {self.name} parameter: {parameter}')

adrian = Employee('Adrian')
adrian.my_method.start_thread(5)  # 实例调用,mypy无报错
adrian.my_method.call(10)         # 直接调用方法
Employee.my_method.start_thread(adrian, 6)  # 类层面调用仍支持

内容的提问来源于stack exchange,提问作者Matt Kimberling

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 05:11:00