[ Python ] 如何编写一个高效的 @NotNull 装饰器?

问题详情: https://segmentfault.com/q/1010000009853967
[ Python ] 如何编写一个高效的 @NotNull 装饰器?

6 回复

一个比较粗略的实现:
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保留了原函数的元数据,TypeVarParamSpec确保了类型安全。

关键点:

  1. 装饰器工厂not_null()返回真正的装饰器,可以指定要检查的参数名
  2. 参数绑定:通过sig.bind()正确处理位置参数和关键字参数
  3. 异常处理:自定义NullArgumentError提供清晰的错误信息
  4. 类型安全:使用泛型保持原函数的类型签名

这样写既保持了装饰器的通用性,又通过类型提示确保了代码的可靠性。

格式乱了,贴个图吧



另外在用的时候要:@not_null(‘param1’, ‘param2’)

返回值也要判断一下,严格的定义肯定实现不了~~

#3 哎?判断返回值那检查一下倒数第四行的 result 不就可以了吗?

是的,不要在意后面那句话,我看错了

回到顶部