在 python 装饰器中引发异常是一个好的模式吗?

Is it a good pattern to raise exceptions in a python decorator?

Context:我为不同的 API 端点定义了 Flask 路由,每个端点调用一个带有特定参数的控制器 class(uid,project_id,等等)。

@app.route('/sample/route', methods=['POST'])
@require_json_payload
@require_fields({
    'pid',
    'params'
})
def route_handler(arg1, arg2):
    #input filtering
    ...

    try:
        proj_cntr.sample_method(
            pid         = pid,
            ...         = ...
        )
    except ProjCntrException:
        #handle error

    #response generation
    ...

控制器 (proj_cntr) 负责确定给定的 PID 是否有效,是否允许给定的用户执行操作,以及其他业务逻辑验证。

我注意到我 c/pasting 在不同的控制器中有很多这样的代码:

if not project_object:
    sys_logger.info('...')
    raise ProjCntrException('PID %d does not exist' % pid)

将这些检查(验证)放在装饰器中似乎是最好的做法。但我不确定如果验证未通过,哪种错误处理模式是最佳实践。

1) 我应该为每个要引发的装饰器创建特定的自定义异常(InvalidProjException、PermissionsException 等)吗?

顾虑:调用方法的catch块会显得臃肿。另外,假设调用者知道被调用者的装饰器引发了什么异常是不是很好?

2)装饰器将一个额外的错误参数传递给方法,方法决定引发什么异常。这样调用方方法就知道期望和处理什么异常类型。

担忧:方法似乎有点过度设计和混乱。

抱歉这个冗长的问题。非常感谢任何thoughts/ideas。

选项 1:为您的装饰器创建一个特定的 Exception 似乎是合乎逻辑的,因为这将帮助用户知道引发了哪个异常(如果有)以及原因.当然,异常类型应该出现在装饰器文档字符串中,以便调用者能够知道会发生什么。这样你就不会假设用户知道引发了什么异常但他已经阅读了文档 - 这更可能是假设。但是,如果经常调用该方法,则调用者每次都必须处理它。这是使用库、API 或其他人的代码时的一部分。

选项2:抛出哪个异常不属于用户自己决定。例如,用户应该通过文档了解它。就 API 设计而言,它是混乱且违反直觉的。

考虑到您的问题,这让我想到了 Java 的工作方式,尤其是如果您使用 IDE,在调用方法时您会收到警告 - IDE并通过输入机制——你应该 try/catch 给定类型的异常。

但在您的情况下,不可能用特定消息引发适当的 HTTPException

我最终使用了装饰器并在其中抛出了特定的异常。例如:

@validate_pid 装饰器 raises InvalidPidException() 被调用装饰方法的任何消费者的 except 块捕获。

目前的优势:

  • 控制器更简洁,代码复制也更少。
  • 相当通用的解决方案,因为我在我的代码中使用了这些 "business logic validation" 装饰器。

目前的缺点:

  • 装饰器依赖于调用装饰方法时传递的某些键名参数,但其中一些参数不会在方法本身内使用。这会导致方法签名中出现一些奇怪的 "dangling" 参数。
  • 我有一些小的性能问题。例如:项目验证装饰器初始化一个会话并加载一个对象(项目),仅在装饰方法本身中再次加载。只是看起来有点简陋。