如何为注入default参数的装饰器修正类型提示消除mypy报错
问题
我想要编写一个装饰器,用于捕获被装饰函数抛出的异常并返回默认值。目前已完成实现,但在调用被装饰函数时传入default关键字参数会触发mypy错误:error: Unexpected keyword argument "default" for "foo" [call-arg]。我希望通过类型提示将注入的default参数添加到被装饰方法的签名中,同时保留两种设置默认值的方式:既可以在装饰器上设置default参数,也可以在调用被装饰函数时传入default关键字参数(便于单元测试)。
当前装饰器及使用示例代码如下:
import functools from typing import TypeVar, ParamSpec, Callable P = ParamSpec("P") R = TypeVar("R") def suppress( default: object = None, ) -> Callable[[Callable[P, R]], Callable[P, R | object]]: def decorator_suppress(func: Callable[P, R]) -> Callable[P, R | object]: functools.wraps( func, ) def wrapper_suppress( *args: P.args, default: object = default, **kwargs: P.kwargs, ) -> R | object: try: return func(*args, **kwargs) except Exception as e: return default return wrapper_suppress return decorator_suppress
使用示例:
@suppress(default="foo!") def foo(a: int, b: int) -> int: return a + b a = foo(1, 1) b = foo(1, "2") c = foo(1, "2", default="bar!") print(a) print(b) print(c)
运行结果:
2 foo! bar!
解决方案
要解决mypy的参数报错问题,需要通过类型系统明确告知被装饰后的函数支持新增的default参数。核心是利用ParamSpec的Concatenate功能合并原函数参数与新增参数,同时通过重载确保类型提示的精确性。
修正后的装饰器代码如下:
import functools from typing import TypeVar, ParamSpec, Callable, Concatenate, overload P = ParamSpec("P") R = TypeVar("R") D = TypeVar("D") @overload def suppress(default: D) -> Callable[[Callable[P, R]], Callable[Concatenate[P.args, P.kwargs, {"default": D}], R | D]]: ... @overload def suppress() -> Callable[[Callable[P, R]], Callable[Concatenate[P.args, P.kwargs, {"default": object | None}], R | object | None]]: ... def suppress( default: object | None = None, ) -> Callable[[Callable[P, R]], Callable]: def decorator_suppress(func: Callable[P, R]) -> Callable: @functools.wraps(func) def wrapper_suppress( *args: P.args, default: object | None = default, **kwargs: P.kwargs, ) -> R | object | None: try: return func(*args, **kwargs) except Exception: return default return wrapper_suppress return decorator_suppress
关键修改点:
- 参数签名合并:通过
Concatenate将原函数的参数列表与新增的default关键字参数合并,让mypy识别到被装饰函数的新签名。 - 类型变量
D:专门捕获装饰器传入的default参数类型,保证返回值类型为原函数返回类型R与D的联合类型,类型提示更精准。 - 重载装饰器:用
@overload分别定义传入default和不传入的两种情况,覆盖不同使用场景的类型检查需求。 - 修正返回类型:装饰器返回的函数类型明确包含新增的
default参数,彻底解决Unexpected keyword argument错误。
修正后,原示例中的两种默认值设置方式都能正常通过mypy检查:
- 装饰器层面预设默认值:
@suppress(default="foo!") - 调用时临时指定默认值:
foo(1, "2", default="bar!")
内容的提问来源于stack exchange,提问作者vicwes
相关产品推荐
相关产品推荐

