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

Python 3.11中如何为带共享参数的装饰器标注Callable类型?

装饰器共享关键字参数的类型注解方案(Python 3.11 + mypy 0.991)

能否保留被装饰函数原签名实现类型检查?

不能。mypy对函数签名与Protocol的匹配规则非常严格:当装饰器要求被装饰函数必须接收共享参数a、b时,若函数签名中未显式声明这些参数,mypy会直接判定类型不兼容——它无法自动推断函数是否能接受未声明的关键字参数,因此无法在完全保留原签名(不加**kw)的前提下通过检查并验证共享参数。

最佳替代方案

以下是几种兼顾类型检查严谨性与代码简洁性的方案:

1. 用ParamSpec + Concatenate绑定共享参数(推荐)

通过ParamSpec捕获被装饰函数的自定义参数,再用Concatenate将共享参数与自定义参数合并,既保留被装饰函数的自定义参数签名,又能让mypy强制检查共享参数是否传入:

from typing import Callable, ParamSpec, TypeVar, Concatenate

P = ParamSpec("P")
T = TypeVar("T")

def my_decorator(func: Callable[Concatenate[dict[str, int], P], T]) -> Callable[Concatenate[int, str, P], T]:
    def wrapper(a: int, b: str, **kwargs: P.kwargs) -> T:
        # 处理共享参数a、b
        shared = {"a": a, "b": b}
        # 转发所有参数给被装饰函数
        return func(**shared, **kwargs)
    return wrapper

# 被装饰函数仅声明自定义参数
@my_decorator
def decorated_func(c: bool, **kwargs) -> str:
    return f"a={kwargs['a']}, b={kwargs['b']}, c={c}"

# mypy会强制检查必须传入a、b、c
decorated_func(a=1, b="test", c=True)  # 合法
decorated_func(c=True)  # mypy报错:缺少a和b参数

如果希望被装饰函数显式使用a、b,也可以直接在函数签名中声明,mypy同样会兼容:

@my_decorator
def decorated_func(a: int, b: str, c: bool) -> str:
    return f"a={a}, b={b}, c={c}"

2. 被装饰函数显式声明共享参数(最直接)

如果允许每个被装饰函数显式写出共享参数a、b,类型检查会最清晰,mypy能完全覆盖所有参数的合法性验证:

from typing import Callable, TypeVar

T = TypeVar("T")

def my_decorator(func: Callable[[int, str, ...], T]) -> Callable[[int, str, ...], T]:
    def wrapper(a: int, b: str, **kwargs) -> T:
        return func(a, b, **kwargs)
    return wrapper

@my_decorator
def decorated_func(a: int, b: str, c: bool) -> str:
    return f"a={a}, b={b}, c={c}"

decorated_func(a=1, b="test", c=True)  # 合法
decorated_func(c=True)  # mypy报错

3. Protocol配合**kwargs妥协方案

定义Protocol要求函数接受a、b和任意额外关键字参数,被装饰函数只需添加**kwargs(无需显式声明a、b),即可通过mypy的类型检查:

from typing import Protocol, TypeVar, Callable

T = TypeVar("T")

class MyCallable(Protocol[T]):
    def __call__(self, *, a: int, b: str, **kwargs) -> T: ...

def my_decorator(func: MyCallable[T]) -> MyCallable[T]:
    def wrapper(*, a: int, b: str, **kwargs) -> T:
        return func(a=a, b=b, **kwargs)
    return wrapper

@my_decorator
def decorated_func(*, c: bool, **kwargs) -> str:
    return f"a={kwargs['a']}, b={kwargs['b']}, c={c}"

decorated_func(a=1, b="test", c=True)  # 合法
decorated_func(c=True)  # mypy报错

总结

  • 若要完全避免被装饰函数显式声明共享参数,优先选择ParamSpec + Concatenate的组合,既能保留自定义参数签名,又能实现严格的类型检查;
  • 若允许显式声明共享参数,直接写在函数签名里是最清晰、最易维护的方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 15:20:22