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

如何为接收被装饰函数参数子集的装饰器做类型注解?

解决带参数子集校验的装饰器类型注解问题

核心思路

要实现支持参数子集校验的装饰器,需利用typing.ParamSpec和typing.Concatenate关联被装饰函数的完整参数与校验函数的参数子集,结合Callable和TypeVar约束返回类型,同时规避ParamSpec不支持绑定的特性。

可通过mypy检查的实现代码

from typing import Callable, ParamSpec, TypeVar, Concatenate

# 定义被装饰函数的参数规范和返回值类型
P = ParamSpec("P")
R = TypeVar("R")
# 定义校验函数的参数子集规范
CheckP = ParamSpec("CheckP")

def validate(*checks: Callable[CheckP, None]) -> Callable[[Callable[Concatenate[CheckP, P], R]], Callable[Concatenate[CheckP, P], R]]:
    def decorator(func: Callable[Concatenate[CheckP, P], R]) -> Callable[Concatenate[CheckP, P], R]:
        def wrapper(*args: CheckP.args, **kwargs: CheckP.kwargs, /, *extra_args: P.args, **extra_kwargs: P.kwargs) -> R:
            # 遍历执行所有校验函数,传入对应的参数子集
            for check in checks:
                # 提取校验函数所需的关键字参数
                filtered_kwargs = {k: v for k, v in kwargs.items() if k in check.__code__.co_varnames}
                check(*args, **filtered_kwargs)
            # 调用原函数,传递所有参数
            return func(*args, *extra_args, **kwargs, **extra_kwargs)
        return wrapper
    return decorator

# 示例校验函数:仅校验age参数
def check_age(age: int) -> None:
    if age <= 18:
        raise ValueError("Age must be greater than 18")

# 被装饰函数:接收name和age两个参数
@validate(check_age)
def greet(name: str, age: int) -> str:
    return f"Hello {name}, you are {age} years old"

# 测试用例
greet("Alice", 20)  # 正常执行
# greet("Bob", 17)  # 触发校验失败,抛出ValueError

关键说明

  • ParamSpec错误修复:你遇到的"Only the first argument to ParamSpec has defined semantics"错误,是因为尝试给ParamSpec添加bound参数导致的——ParamSpec设计上仅接受名称作为唯一参数,不支持绑定操作。我们通过分别定义CheckP(校验参数子集)和P(剩余参数),再用Concatenate组合,明确参数结构关系。
  • 参数子集提取:通过check.__code__.co_varnames获取校验函数的参数名,从被装饰函数的输入中过滤出对应参数,确保校验函数只拿到它需要的参数子集。
  • 参数边界区分:使用/分隔校验参数和剩余参数,让mypy能正确识别参数子集的边界,避免类型推断混乱。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 09:27:04