使用Python装饰器时如何强制使用关键字参数?
问题:带参数验证的装饰器破坏强制关键字参数约束
我需要给SDK中的多个Python函数实现通用参数验证装饰器,这些函数参数类似但顺序未必一致,且必须保持参数可读性(不能仅依赖*args和**kwargs)。
现有一个用于验证a > b的装饰器:
from functools import wraps def check_args(f): @wraps(f) def decorated_function(self, *args, **kwargs): a = kwargs["a"] b = kwargs["b"] if a < b: raise ValueError("a must be strictly greater than b") return f(self, *args, **kwargs) return decorated_function
将其应用在带强制关键字参数的方法上:
class MyClass(object): @check_args def f(self, *, a, b): return a + b
- 用关键字参数调用
MyClass().f(a=2, b=1)时,符合预期无错误; - 用位置参数调用
MyClass().f(1, 2)时,装饰器抛出KeyError,但移除装饰器后,Python会抛出正确的TypeError(提示必须使用关键字参数)。
需要解决:如何让装饰器不破坏原函数的强制关键字参数约束,同时实现通用的参数验证?
正规解决方案:利用inspect模块解析函数签名
Python官方的inspect模块可以解析函数的参数签名,将传入的args和kwargs统一绑定到签名上,既保留原函数的参数校验逻辑,又能让装饰器通用地获取参数值。
修改后的装饰器代码
from functools import wraps import inspect def check_args(f): @wraps(f) def decorated_function(self, *args, **kwargs): # 获取原函数的参数签名 sig = inspect.signature(f) try: # 绑定传入参数,触发原函数的参数校验(包括强制关键字参数检查) bound_args = sig.bind(self, *args, **kwargs) # 填充参数默认值 bound_args.apply_defaults() except TypeError as e: # 直接抛出原函数会产生的错误,保持原生错误提示 raise e # 从绑定结果中提取需要验证的参数 a = bound_args.arguments['a'] b = bound_args.arguments['b'] if a < b: raise ValueError("a must be strictly greater than b") return f(self, *args, **kwargs) return decorated_function
效果验证
- 位置参数调用:
MyClass().f(1, 2)会抛出和原函数一致的TypeError: f() takes 1 positional argument but 3 were given,强制用户使用关键字参数; - 非法关键字参数:
MyClass().f(a=1, b=2)会抛出装饰器定义的ValueError: a must be strictly greater than b; - 合法调用:
MyClass().f(a=2, b=1)正常返回结果3。
方案优势
- 完全遵循Python原生参数校验逻辑,错误提示和原生行为一致;
- 基于官方
inspect模块实现,通用性强,适配不同参数顺序的函数; - 保留原函数的参数可读性,无需修改函数定义为
*args/**kwargs。
内容的提问来源于stack exchange,提问作者vgoklani
相关产品推荐
相关产品推荐

