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

为含*args与关键字-only参数的Callable标注类型遇mypy报错求解

解决类方法调用次数统计装饰器的Mypy类型不匹配问题

首先明确:typing.Callable无法直接指定关键字-only参数,必须通过typing.Protocol来定义符合要求的可调用类型。你之前用Protocol出错,大概率是没正确匹配类方法的签名(比如遗漏self参数,或者关键字-only参数的写法不对)。

下面是能让Mypy通过的完整实现方案:

1. 定义适配类方法的Protocol

先定义一个专门针对类方法的Protocol,明确包含self参数和关键字-only的log_name:

from typing import Protocol, TypeVar, Any, ParamSpec

P = ParamSpec("P")
R = TypeVar("R")

class ClassMethodWithLogName(Protocol):
    def __call__(self, *args: P.args, log_name: str, **kwargs: P.kwargs) -> R:
        ...

这里用ParamSpec和TypeVar来泛化方法的参数和返回值,保证装饰器能适配不同签名的类方法。

2. 实现类型正确的装饰器

装饰器需要接收上述Protocol类型的方法,返回一个新的类方法,同时处理调用次数统计:

call_counts: dict[str, int] = {}

def count_calls(method: ClassMethodWithLogName[P, R]) -> ClassMethodWithLogName[P, R]:
    def wrapper(self: Any, *args: P.args, log_name: str, **kwargs: P.kwargs) -> R:
        if log_name not in call_counts:
            call_counts[log_name] = 0
        call_counts[log_name] += 1
        return method(self, *args, log_name=log_name, **kwargs)
    return wrapper

这里wrapper严格遵循Protocol的签名:self作为第一个参数,log_name是关键字-only,其余参数用ParamSpec泛化。

3. 在类中使用装饰器

这样使用时Mypy不会报错,且log_name必须以关键字参数传入:

class MyService:
    @count_calls
    def process_data(self, data: list[int], log_name: str) -> bool:
        # 业务逻辑
        return len(data) > 0

# 正确调用(必须用关键字传log_name)
service = MyService()
service.process_data([1,2,3], log_name="data_process")

# 错误调用(位置传log_name会被Mypy报错)
# service.process_data([1,2,3], "data_process")

关键说明

  • 为什么不用普通Callable?因为Callable的语法Callable[[Arg1, Arg2], Return]只能定义位置参数,无法标记关键字-only参数。
  • Protocol的核心是匹配__call__方法的签名:必须包含类方法的self,用log_name: str直接声明为关键字-only(也可以用*, log_name: str明确分隔位置参数和关键字-only参数,效果一致)。
  • 用ParamSpec和TypeVar是为了让装饰器不丢失原方法的参数和返回值类型信息,保证Mypy能正确推断。

如果你的装饰器需要支持静态方法或普通函数,只需调整Protocol的__call__签名(比如去掉self参数)即可。

内容的提问来源于stack exchange,提问作者Paulo-99

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 09:11:06