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