[ Python ] 如何编写一个高效的 @NotNull 装饰器?
问题详情: https://segmentfault.com/q/1010000009853967
[ Python ] 如何编写一个高效的 @NotNull 装饰器?
一个比较粗略的实现:
def not_null(*varnames):
def outer(func):
def inner(*args, **kwargs):
args_name = func.code.co_varnames
args_cnt = func.code.co_argcount # 没用到
flag = True
for name in varnames: # 这里还要考虑 varnames 中有 args 中没有的参数
index = args_name.index(name) # 还可以加上考虑 kwargs 参数
flag = flag and (args[index] is not None)
if not flag:
raise TypeError(‘something is None’)
result = func(*args, **kwargs)
return result
return inner
return outer
from functools import wraps
from typing import Any, Callable, TypeVar, ParamSpec, cast
P = ParamSpec('P')
R = TypeVar('R')
class NullArgumentError(ValueError):
"""自定义异常,用于表示参数为None的错误"""
pass
def not_null(*arg_names: str) -> Callable[[Callable[P, R]], Callable[P, R]]:
"""
装饰器工厂函数,检查指定参数是否为None
Args:
*arg_names: 要检查的参数名称列表
Returns:
装饰器函数
"""
def decorator(func: Callable[P, R]) -> Callable[P, R]:
# 获取函数签名信息
import inspect
sig = inspect.signature(func)
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> R:
# 绑定参数到签名
bound_args = sig.bind(*args, **kwargs)
bound_args.apply_defaults()
# 检查指定的参数
for arg_name in arg_names:
if arg_name in bound_args.arguments:
value = bound_args.arguments[arg_name]
if value is None:
raise NullArgumentError(
f"参数 '{arg_name}' 不能为None (函数: {func.__name__})"
)
return func(*args, **kwargs)
return cast(Callable[P, R], wrapper)
return decorator
# 使用示例
@not_null('name', 'value')
def process_data(name: str, value: int, optional: str = None) -> str:
"""处理数据的示例函数"""
return f"{name}: {value} (optional: {optional})"
# 测试
try:
print(process_data("test", 100)) # 正常
print(process_data("test", 100, "extra")) # 正常
print(process_data(None, 100)) # 抛出异常
except NullArgumentError as e:
print(f"错误: {e}")
try:
print(process_data("test", None)) # 抛出异常
except NullArgumentError as e:
print(f"错误: {e}")
这个装饰器的核心思路是:使用inspect.signature获取函数签名,在调用时绑定参数并检查指定参数是否为None。@wraps保留了原函数的元数据,TypeVar和ParamSpec确保了类型安全。
关键点:
- 装饰器工厂:
not_null()返回真正的装饰器,可以指定要检查的参数名 - 参数绑定:通过
sig.bind()正确处理位置参数和关键字参数 - 异常处理:自定义
NullArgumentError提供清晰的错误信息 - 类型安全:使用泛型保持原函数的类型签名
这样写既保持了装饰器的通用性,又通过类型提示确保了代码的可靠性。
格式乱了,贴个图吧
另外在用的时候要:@not_null(‘param1’, ‘param2’)
返回值也要判断一下,严格的定义肯定实现不了~~
#3 哎?判断返回值那检查一下倒数第四行的 result 不就可以了吗?
是的,不要在意后面那句话,我看错了

