如何为装饰器添加类型提示以限定被装饰函数必须包含的部分参数?
如何为装饰器添加类型提示以限定被装饰函数必须包含的部分参数?
嘿,我懂你的需求——你想给这个装饰器加上类型提示,确保被它装饰的函数必须包含a: int和b: str这两个必填前置参数,同时装饰器完全不改动原函数的签名对吧?你之前用ParamSpec的尝试踩了MyPy的规则坑,咱们来一步步把它弄对。
先说说你之前出错的原因
你之前的写法把ParamSpec放在了Callable的参数列表中间,这不符合MyPy对ParamSpec的使用规则:它只能用来表示完整的剩余参数集合,不能插在固定参数中间。正确的做法是用它来捕获原函数除了固定前置参数之外的所有额外参数。
正确的类型提示写法(兼容多版本)
我们可以用ParamSpec捕获剩余参数,再用TypeVar捕获返回值类型,确保装饰器返回的函数和原函数签名完全一致:
from functools import wraps from typing import Callable, ParamSpec, TypeVar # 定义类型变量:捕获被装饰函数的返回值类型 _R = TypeVar("_R") # 定义参数规格变量:捕获除a、b之外的剩余参数 _P = ParamSpec("_P") def my_decorator( func: Callable[[int, str], _R] | Callable[[int, str, *_P.args, **_P.kwargs], _R] ) -> Callable[[int, str, *_P.args, **_P.kwargs], _R]: @wraps(func) def wrapper(a: int, b: str, *args: _P.args, **kwargs: _P.kwargs) -> _R: # 这里可以添加你的装饰器逻辑,比如参数校验、日志等 return func(a, b, *args, **kwargs) return wrapper
写法解释:
_R:用来“继承”原函数的返回值类型,保证装饰后函数的返回值类型和原函数一致。_P:用来捕获原函数除了a和b之外的所有位置参数和关键字参数。- 联合类型
Callable[[int, str], _R] | ...:同时兼容两种情况——被装饰函数只有a和b两个参数,或者还有额外参数。
更优雅的写法(Python 3.10+)
如果你用的是Python 3.10及以上版本,可以用Concatenate来更清晰地表达“固定前置参数+任意剩余参数”的结构,写法更简洁:
from functools import wraps from typing import Callable, ParamSpec, TypeVar, Concatenate _R = TypeVar("_R") _P = ParamSpec("_P") def my_decorator( func: Callable[Concatenate[int, str, _P], _R] ) -> Callable[Concatenate[int, str, _P], _R]: @wraps(func) def wrapper(a: int, b: str, *args: _P.args, **kwargs: _P.kwargs) -> _R: return func(a, b, *args, **kwargs) return wrapper
Concatenate的作用就是把固定参数列表和ParamSpec拼接起来,明确告诉MyPy:被装饰函数必须以int类型的a和str类型的b开头,后面可以跟着任意参数。这种写法MyPy的支持更完善,可读性也更强。
测试一下效果
符合要求的函数(MyPy不会报错):
# 只有a和b两个参数的函数 @my_decorator def func1(a: int, b: str) -> float: return float(a) + len(b) # 带有额外参数的函数 @my_decorator def func2(a: int, b: str, c: bool, *args: int, **kwargs: str) -> float: base = float(a) + len(b) if c: base += sum(args) return base
不符合要求的函数(MyPy会抛出错误):
# 错误:缺少必填参数b @my_decorator def bad_func1(a: int) -> float: return float(a) # 错误:参数a的类型不符合要求(应该是int,写成了str) @my_decorator def bad_func2(a: str, b: str) -> float: return len(a) + len(b)
最后总结一下关键点
ParamSpec用来捕获剩余参数,不能插在固定参数中间;TypeVar用来捕获返回值类型,保证装饰前后函数返回类型一致;- 优先用
Concatenate(Python 3.10+)来表达固定前置参数的要求,写法更清晰。
备注:内容来源于stack exchange,提问作者Philip Couling
相关产品推荐
相关产品推荐

