如何修改Python装饰器添加的函数签名,解决Pylint报错并让VSCode识别新增可选参数?
我最近写了个叫add_suffix的Python装饰器,作用是给目标函数加一个suffix关键字参数,运行起来没问题,但Pylint一直报E1123: Unexpected keyword argument 'suffix'的错,而且VSCode也识别不到这个新增的可选参数,这可太头疼了!先看看我的代码:
import functools def add_suffix(func): @functools.wraps(func) def wrapper(*args, suffix: str = '!', **kwargs): return f'{func(*args, **kwargs)}{suffix}' return wrapper @add_suffix def my_function(): return '1' # E1123: Unexpected keyword argument 'suffix' in method call (unexpected-keyword-arg) print(my_function(suffix='!!'))
别着急,我来给你两种解决方案,都能解决这个问题:
方案一:用现代类型注解(推荐,Python 3.10+)
我们可以用typing模块里的ParamSpec、TypeVar和Concatenate来给装饰器做精确的类型注解,让类型检查工具和编辑器清楚知道装饰器给函数新增了参数。
修改后的代码如下:
import functools from typing import ParamSpec, TypeVar, Callable, Concatenate # 定义参数规格和返回值类型变量 P = ParamSpec('P') R = TypeVar('R') def add_suffix(func: Callable[P, R]) -> Callable[Concatenate[P.args, P.kwargs, {'suffix': str}], str]: @functools.wraps(func) def wrapper(*args: P.args, suffix: str = '!', **kwargs: P.kwargs) -> str: return f'{func(*args, **kwargs)}{suffix}' return wrapper @add_suffix def my_function(): return '1' # 现在Pylint不会报错,VSCode也能识别suffix参数了 print(my_function(suffix='!!'))
这里的ParamSpec用来捕获原函数的参数规格,Concatenate用来把原函数的参数和新增的suffix参数合并起来,这样类型检查工具就能准确识别到装饰后的函数支持suffix参数了。
方案二:修改函数注解(快速hack方法)
如果你不想用复杂的类型注解,也可以直接给装饰后的函数添加参数注解,让编辑器和Pylint识别到suffix参数:
import functools def add_suffix(func): @functools.wraps(func) def wrapper(*args, suffix: str = '!', **kwargs): return f'{func(*args, **kwargs)}{suffix}' # 把suffix参数的注解添加到wrapper的注解里 wrapper.__annotations__['suffix'] = str return wrapper @add_suffix def my_function(): return '1' print(my_function(suffix='!!'))
这种方法通过修改函数的__annotations__属性,手动添加suffix的类型注解,虽然简单,但不如方案一规范,适合快速解决问题的场景。
另外你提到之前看到ParamSpec的文档说“不能在*args和**kwargs之间放关键字-only参数”,其实我们这里的写法是符合规范的——suffix作为关键字-only参数放在*args之后、**kwargs之前,是Python允许的语法,而类型注解里用Concatenate也正确表达了参数的结构,所以不会有问题。
备注:内容来源于stack exchange,提问作者illusional

