正如您猜对的那样,它有两个方面:通过在 except 之后不指定异常类型来捕获 any 错误,并在不采取任何操作的情况下简单地传递它。
我的解释“有点”长——所以 tl;dr 它分解为:
-
不要发现任何错误。始终指定您准备从哪些异常中恢复并仅捕获这些异常。
-
尽量避免传入除了块。除非明确要求,否则这通常不是一个好兆头。
但让我们详细介绍一下:
不要发现任何错误
使用try 块时,您通常会这样做,因为您知道有可能引发异常。因此,您也已经大致了解 什么 可以破坏以及可以引发什么异常。在这种情况下,您可以捕获异常,因为您可以积极地从中恢复。这意味着您已为例外情况做好了准备,并有一些替代计划,以防发生该例外情况。
例如,当您要求用户输入一个数字时,您可以使用int() 转换输入,这可能会引发ValueError。您只需要求用户再试一次即可轻松恢复,因此捕获ValueError 并再次提示用户将是一个合适的计划。一个不同的例子是,如果您想从文件中读取一些配置,而该文件恰好不存在。因为它是一个配置文件,您可能有一些默认配置作为备用,因此该文件不是必需的。因此,在这里捕获FileNotFoundError 并简单地应用默认配置将是一个不错的计划。现在,在这两种情况下,我们都有一个非常具体的例外,我们期望并且有一个同样具体的计划来从中恢复。因此,在每种情况下,我们都明确地只 except 那个特定的异常。
但是,如果我们要捕获所有内容,那么——除了我们准备从中恢复的那些异常之外——我们也有可能得到我们没有预料到的异常,并且我们确实无法从中恢复;或者不应该从中恢复。
让我们以上面的配置文件为例。如果文件丢失,我们只是应用了默认配置,稍后可能会决定自动保存配置(因此下次,该文件存在)。现在想象我们得到一个IsADirectoryError,或者一个PermissionError。在这种情况下,我们可能不想继续;我们仍然可以应用我们的默认配置,但我们稍后将无法保存文件。并且用户可能也打算进行自定义配置,因此可能不希望使用默认值。所以我们想立即告诉用户它,并且可能也中止程序执行。但这不是我们想要在一些小的代码部分深处做的事情。这是应用程序级别的重要内容,因此应该在顶部处理——所以让异常冒泡吧。
Python 2 idioms 文档中还提到了另一个简单的例子。在这里,代码中存在一个简单的错字导致它中断。因为我们正在捕获每一个异常,我们也捕获了NameErrors 和SyntaxErrors。两者都是我们在编程时都会发生的错误,而且都是我们在发布代码时绝对不想包含的错误。但是因为我们也捕获了那些,我们甚至不会知道它们发生在那里,并且失去了正确调试它的任何帮助。
但也有更危险的例外情况,我们不太可能做好准备。例如,SystemError 通常是很少发生且我们无法真正计划的事情;这意味着发生了一些更复杂的事情,可能会阻止我们继续当前的任务。
在任何情况下,您都不太可能在代码的一小部分中为所有事情做好准备,因此您实际上应该只捕获您准备好的那些异常。有些人建议至少抓住Exception,因为它不会包含像SystemExit 和KeyboardInterrupt 这样设计会终止你的应用程序的东西,但我认为这还很远太不具体了。只有一个地方我个人接受捕获Exception 或任何异常,那就是在一个全局应用程序级异常处理程序中,它的唯一目的是记录我们没有准备好的任何异常为了。这样,我们仍然可以保留关于意外异常的尽可能多的信息,然后我们可以使用这些信息来扩展我们的代码以显式处理这些异常(如果我们可以从它们中恢复),或者在出现错误的情况下创建测试用例以确保它不会再发生了。但是,当然,这只有在我们只捕获那些我们已经预料到的异常时才有效,所以我们没有预料到的那些自然会冒泡。
尽量避免传入除了块
当显式捕获一小部分特定异常时,在很多情况下我们什么都不做就可以了。在这种情况下,只需 except SomeSpecificException: pass 就可以了。但大多数时候,情况并非如此,因为我们可能需要一些与恢复过程相关的代码(如上所述)。例如,这可以是再次重试操作,或者改为设置默认值。
如果不是这样,例如,因为我们的代码已经构造为重复直到成功,那么传递就足够了。以上面的示例为例,我们可能希望要求用户输入一个数字。因为我们知道用户喜欢不做我们要求他们做的事情,所以我们可能一开始就把它放入一个循环中,所以它可能看起来像这样:
def askForNumber ():
while True:
try:
return int(input('Please enter a number: '))
except ValueError:
pass
因为我们一直在尝试直到没有抛出异常,所以我们不需要在 except 块中做任何特殊的事情,所以这很好。但当然,有人可能会争辩说,我们至少想向用户显示一些错误消息,告诉他为什么他必须重复输入。
但在许多其他情况下,仅传递 except 表明我们并没有真正为我们捕获的异常做好准备。除非这些异常很简单(如ValueError 或TypeError),并且我们可以通过的原因很明显,否则尽量避免只是通过。如果真的无事可做(并且您对此非常确定),请考虑添加评论为什么会这样;否则,扩展 except 块以实际包含一些恢复代码。
except: pass
不过,最严重的违规者是两者的结合。这意味着我们愿意捕获任何错误,尽管我们绝对没有为此做好准备并且我们也不会对此采取任何措施。您至少想要记录错误并可能重新引发它以仍然终止应用程序(在 MemoryError 之后您不太可能像往常一样继续)。只是传递不仅会使应用程序保持一定的活力(当然取决于您捕获的位置),而且还会丢弃所有信息,从而无法发现错误 - 如果您不是发现错误的人,尤其如此。
所以底线是:只捕获您真正期望并准备从中恢复的异常;所有其他人可能要么是您应该修复的错误,要么是您无论如何都没有准备好的事情。如果您真的不需要对它们做任何事情,则传递 特定 异常是可以的。在所有其他情况下,这只是假设和懒惰的标志。你肯定想解决这个问题。