【问题标题】:Python try-except block re-raising exceptionPython try-except 块重新引发异常
【发布时间】:2023-03-31 11:21:01
【问题描述】:

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

选项 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 中找不到任何可以说明这一点的内容。

【问题讨论】:

  • 为什么你想在选项 b 中再加注?只要调用 bar ,它就会在任何情况下被提升
  • @Karl 这正是我的问题,如果可以写选项 a。问题是您并不立即知道通过查看代码会引发哪些异常栏。因此,重新加注的一个可能论点是明确的。
  • 我会选择选项 a。如果您想知道异常的来源,您可以随时查看堆栈跟踪
  • 我认为最好的方法是直接在可能发生异常的函数中处理异常(即在“bar”内)。 IE。和选项 b) 一样,但没有再次加注,为什么你叫 "bar(6)"。选项 b) 的问题是每次调用函数时都必须构建执行处理。如果它自己处理异常会更好。
  • 对不起,最后一句应该是“选项a)的问题”

标签: python pep


【解决方案1】:

处理错误

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

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

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

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

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

在某些编程语言中,必须在函数/方法级别声明可能引发的异常。 Python 不同:它是一种脚本语言,缺乏这样的特性。当然,因此有时您可能会非常意外地遇到异常,因为您可能不知道您正在调用的其他代码可能会引发此类异常。但这没什么大不了的:要解决这种情况,您的主程序中有 try...except...

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

  • 记录可能引发的异常;如果编程语言本身在这方面没有帮助,您需要通过提供更广泛的文档来弥补这一不足;
  • 执行广泛的测试;

一般而言,遵循您的选项 b) 毫无意义。事情可能更明确,但代码本身并不是这个明确信息的正确位置。相反,这些信息应该是您的函数/方法文档的一部分。

因此而不是...

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)

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

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

输出错误

无论如何我都不会按照你的方法打印一个简单的错误消息:

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

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

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

【讨论】:

    【解决方案2】:

    如果您阅读 Bob 叔叔的清洁代码书,您应该始终将逻辑和错误处理分开。这将使选项 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)        
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-08-13
      • 2010-10-22
      • 1970-01-01
      • 1970-01-01
      • 2016-02-12
      • 1970-01-01
      • 1970-01-01
      • 2020-06-05
      相关资源
      最近更新 更多