Python try-except 块重新引发异常

Python try-except block re-raising exception

不捕获内部函数的异常而在调用外部函数时捕获异常是不好的做法吗?让我们看两个例子:

选项 a)

def foo(a, b):
    return a / b

def bar(a):
    return foo(a, 0)

try:
    bar(6)
except ZeroDivisionError:
    print("Error!")

专业版:更清洁(我认为)
缺点:如果不查看 foo

,您将无法判断 bar 引发了哪些异常

选项 b)

def foo(a, b):
    return a / b

def bar(a):
    try:
        ret = foo(a, 0)
    except ZeroDivisionError:
        raise

    return ret

try:
    bar(6)
except ZeroDivisionError:
    print("Error!")

专业版:显式
缺点:您只是在编写一个重新引发异常的 try-except 块。在我看来也很丑

其他选项?

我知道如果你想对例外做某事或将几个例外组合在一起,选项 b 是唯一的选择。但是,如果您只想按原样重新提出一些特定的异常怎么办?

我在 PEP 中找不到任何对此有所启发的内容。

如果您阅读 Uncle Bob 的 Clean Code 一书,您应该始终将逻辑和错误处理分开。这将使选项 a.) 成为首选解决方案。

我个人喜欢这样命名函数:

def _foo(a, b):
    return a / b

def try_foo(a, b):
    try:
        return _foo(a, b)
    except ZeroDivisionError:
        print('Error')


if __name__ == '__main__':
    try_foo(5, 0)        

处理错误

这是一种不好的做法吗?在我看来:不,不是。一般来说,这是很好的做法:

def foo(a, b):
    return a / b

def bar(a):
    return foo(a, 0)

try:
    bar(6)
except ZeroDivisionError:
    print("Error!")

原因很简单:处理错误的代码集中在主程序的一个点上。

在某些编程语言中,可能引发的异常必须在 function/method 级别声明。 Python 不同:它是一种脚本语言,缺少这样的功能。当然,有时您可能会出乎意料地遇到异常,因为您可能没有意识到您正在调用的其他代码可能会引发此类异常。但这没什么大不了的:要解决这种情况,您的主程序中有 try...except...

您可以通过以下方式弥补对可能的异常情况的了解不足:

  • 记录可以提出的异常;如果编程语言本身没有帮助,您需要通过提供更广泛的文档来弥补这一缺陷;
  • 执行大量测试;

一般来说,遵循您的选项 b) 完全没有意义。事情可能更明确,但代码本身并不是该明确信息的正确位置。相反,此信息应该是您函数的一部分s/method 的文档。

因此而不是...

def bar(a):
    try:
        ret = foo(a, 0)
    except ZeroDivisionError:
        raise

    return ret

...写:

def bar(a):
    """
    Might raise ZeroDivisionError
    """
    return foo(a, 0)

或者像我这样写:

#
# @throws   ZeroDivisionError       Does not work with zeros.
#
def bar(a):
    return foo(a, 0)

(但是您完全依赖哪种语法来编写文档是完全不同的事情,超出了这个问题的范围。)

在某些情况下,在 function/method 中捕获异常是 一种很好的做法。例如,如果您 想要 一个方法以任何方式成功,即使某些内部操作可能失败也是如此。 (例如,如果你尝试读取一个文件,如果它不存在,你想使用默认数据。)但是捕获异常只是为了再次引发它通常没有任何意义:现在我什至无法提出在这可能有用的情况下(尽管可能有一些特殊情况)。如果您想提供可能引发此类异常的信息,请不要依赖用户查看实现,而是查看您的 function/method.

的文档

输出错误

无论如何我都不会遵循您只打印一条简单错误消息的方法:

try:
    bar(6)
except ZeroDivisionError:
    print("Error!")

提出合理的、人类可读的、简单的错误消息是相当 labor-intensive。我曾经这样做过,但这种方法所需的代码量是巨大的。根据我的经验,最好只是失败并打印出堆栈跟踪。有了这个堆栈跟踪,通常任何人都可以很容易地找到错误的原因。

不幸的是Python 没有在错误输出中提供非常可读的堆栈跟踪。为了弥补这一点,我实现了自己的错误输出处理(可作为模块重用),甚至使用颜色,但这是另一回事,可能也超出了这个问题的范围。