如何编写通过mypy严格检查的Python去首参装饰器
实现类型安全的“丢弃首个参数”装饰器
问题根源
你当前的代码框架逻辑是对的,但args[1:]会被mypy推断为泛型tuple[object, ...],无法和P.args的具体类型匹配,这才触发了类型错误。要解决这个问题,需要让类型检查器明确知道切片后的参数元组类型就是P.args。
修复后的代码
通过类型断言可以直接告诉mypy切片结果的类型,同时保留原有的类型结构:
from functools import wraps from typing import Any, Callable, Concatenate, ParamSpec, TypeVar, cast P = ParamSpec('P') R = TypeVar('R') def drop_first_arg(f: Callable[Concatenate[Any, P], R]) -> Callable[P, R]: @wraps(f) def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: # 用cast断言切片后的参数完全匹配P.args的类型 return f(*cast(P.args, args[1:]), **kwargs) return wrapper
验证用例
下面的代码可以验证装饰器的类型正确性和功能:
# 原函数:首个参数为占位符(比如类方法的self),后续是业务参数 def demo_func(placeholder: object, x: int, y: str, z: bool = False) -> str: return f"{x}-{y}-{z}" # 应用装饰器 trimmed_func = drop_first_arg(demo_func) # 正确调用:mypy会自动提示参数类型 print(trimmed_func(10, "hello")) # 输出 "10-hello-False" print(trimmed_func(20, "world", z=True)) # 输出 "20-world-True" # 错误调用会触发mypy报错 # trimmed_func("not_int", "oops") # mypy: Argument 1 has incompatible type "str"; expected "int"
关键说明
cast(P.args, args[1:]):这是核心修复——因为mypy无法自动推导args[1:]的具体类型,我们用cast明确告知它切片结果和P.args完全一致。Concatenate[Any, P]:准确描述原函数的参数结构:第一个参数是任意类型,后续参数遵循P的参数规格。Callable[P, R]:装饰后的函数只暴露P定义的参数,返回类型与原函数保持一致。
修改后,运行mypy --strict将不会再出现类型错误,同时完整保留了原函数的参数类型提示。
内容的提问来源于stack exchange,提问作者Ru Hasha
相关产品推荐
相关产品推荐

