为什么“except: pass”是一种糟糕的编程习惯?

新手上路,请多包涵

我经常看到关于如何不鼓励使用 except: pass 的其他 Stack Overflow 问题的评论。为什么这样不好?有时我只是不关心错误是什么,我只想继续编写代码。

 try:
    something
except:
    pass

为什么使用 except: pass 块不好?是什么让它变坏了?是我 pass 出错还是我 except 有任何错误?

原文由 Vader 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 448
2 个回答

正如您猜对的那样,它有两个方面:通过在 except 之后指定无异常类型来捕获 任何 错误,并简单地传递它而不采取任何操作。

我的解释“有点”长——所以 tl;dr 它分解为:

  1. 不要 发现任何 错误。始终指定您准备从哪些异常中恢复并仅捕获这些异常。
  2. 尽量避免传入 except blocks 。除非明确要求,否则这通常不是一个好兆头。

但让我们进入细节:

不要 发现任何 错误

当使用 try 块时,您通常会这样做,因为您知道有可能抛出异常。因此,您也已经大致了解 什么 可以中断以及可以抛出什么异常。在这种情况下,您会捕获异常,因为您可以从中 _恢复_。这意味着您已为例外情况做好准备,并有一些替代计划,您将在发生该例外情况时遵循这些计划。

例如,当您要求用户输入数字时,您可以使用 int() 转换输入,这可能会引发 ValueError 。您可以通过简单地要求用户再次尝试来轻松恢复它,因此捕获 ValueError 并再次提示用户将是一个合适的计划。另一个例子是,如果您想从文件中读取一些配置,而该文件恰好不存在。因为它是一个配置文件,你可能有一些默认配置作为后备,所以这个文件并不是必需的。因此,捕获 FileNotFoundError 并简单地应用默认配置将是一个不错的计划。现在,在这两种情况下,我们都有一个我们期望的非常具体的异常,并且有一个同样具体的计划来从中恢复。因此,在每种情况下,我们明确地只显示 except 那个特定的 异常。

然而,如果我们要捕获 _所有内容_,那么——除了我们准备从中恢复的那些异常——我们还有可能得到我们没有预料到的异常,并且我们确实无法从中恢复;或者不应该从中恢复。

让我们以上面的配置文件为例。在丢失文件的情况下,我们只是应用我们的默认配置,并可能在稍后决定自动保存配置(因此下次,该文件存在)。现在假设我们得到一个 IsADirectoryErrorPermissionError 代替。在这种情况下,我们可能不想继续;我们仍然可以应用我们的默认配置,但我们以后将无法保存文件。并且用户很可能也想要自定义配置,因此可能不需要使用默认值。所以我们想立即告诉用户它,并且可能也中止程序执行。但这不是我们想要在某个小代码部分的深处做的事情;这是应用程序级别的重要性,因此它应该在顶部处理——所以让异常冒泡。

Python 2 idioms 文档中也提到了另一个简单的例子。在这里,代码中存在一个简单的拼写错误,导致它中断。因为我们正在捕获 每个 异常,所以我们还捕获 NameError sSyntaxError s 。这两个都是我们在编程时都会遇到的错误,而且都是我们在发布代码时绝对不想包含的错误。但是因为我们也捕获了那些,我们甚至不知道它们在那里发生并且失去了正确调试它的任何帮助。

但也有更危险的例外情况,我们不太可能为此做好准备。例如, SystemError 通常很少发生并且我们无法真正计划;这意味着有更复杂的事情正在发生,可能会阻止我们继续当前的任务。

在任何情况下,您都不太可能在代码的一小部分为所有内容做好准备,因此实际上您应该只捕获那些您准备好的异常。有些人建议至少抓住 Exception 因为它不会包括像 SystemExitKeyboardInterrupt 这样 东西这仍然太不具体了。只有一个地方我个人接受捕获 Exception任何 异常,那就是在一个全局应用程序级异常处理程序中,它的唯一目的是记录我们没有准备好的任何异常。这样,我们仍然可以保留尽可能多的关于意外异常的信息,然后我们可以使用这些信息来扩展我们的代码以显式处理这些异常(如果我们可以从中恢复)或者——在出现错误的情况下——创建测试用例以确保它不会再发生了。但是,当然,这只有在我们只捕捉到那些我们已经预料到的异常时才有效,所以那些我们没有预料到的异常自然会冒出来。

尽量避免传入 except 块

当显式捕获一小部分特定异常时,在很多情况下我们什么都不做就可以了。在这种情况下,只 except SomeSpecificException: pass 就好了。但大多数时候,情况并非如此,因为我们可能需要一些与恢复过程相关的代码(如上所述)。例如,这可以是再次重试操作,或者设置默认值。

如果不是这种情况,例如,因为我们的代码已经构造为重复直到成功,那么只要通过就足够了。以我们上面的例子为例,我们可能想要求用户输入一个数字。因为我们知道用户不喜欢我们要求他们做的事情,所以我们可能首先将其放入一个循环中,所以它看起来像这样:

 def askForNumber ():
    while True:
        try:
            return int(input('Please enter a number: '))
        except ValueError:
            pass

因为我们一直尝试直到没有异常抛出,所以我们不需要在 except 块中做任何特殊的事情,所以这很好。但是,当然,有人可能会争辩说我们至少要向用户显示一些错误消息以告诉他为什么必须重复输入。

但在许多其他情况下,只是传入 except 表明我们并没有真正为我们正在捕获的异常做好准备。除非这些异常很简单(比如 ValueErrorTypeError ),并且我们可以通过的原因很明显,否则尽量避免只是通过。如果真的无事可做(并且您对此非常确定),请考虑添加评论为什么会这样;否则,扩展 except 块以实际包含一些恢复代码。

except: pass

最糟糕的罪犯是两者的结合。这意味着我们愿意捕获 任何 错误,尽管我们绝对没有为此做好准备 并且 我们也没有对此做任何事情。您 至少 想要记录错误并可能重新引发它以仍然终止应用程序(在 MemoryError 之后您不太可能像往常一样继续)。只是通过不仅可以使应用程序保持一定的活力(当然取决于您捕获的位置),而且还会丢弃所有信息,从而无法发现错误——如果您不是发现错误的人,则尤其如此。


所以底线是:只捕获你真正期望并准备好从中恢复的异常;所有其他人可能要么是你应该纠正的错误,要么是你无论如何都没有准备好的。如果你真的不需要对它们做些什么,传递 特定 的异常是可以的。在所有其他情况下,这只是自以为是和懒惰的表现。你肯定想解决这个问题。

原文由 poke 发布,翻译遵循 CC BY-SA 4.0 许可协议

这里的主要问题是它忽略了所有错误:内存不足、CPU 正在燃烧、用户想要停止、程序想要退出、Jabberwocky 正在杀死用户。

这太过分了。在你的脑海中,你在想“我想忽略这个网络错误”。如果 出现意想不到 的错误,那么您的代码将以完全不可预测的方式悄无声息地继续并中断,没有人可以调试。

这就是为什么你应该限制自己只忽略一些错误,让其余的通过。

原文由 Aaron Digulla 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题