一尘不染

Python-为什么“ except:pass”是不好的编程习惯?

python

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

try:
    something
except:
    pass

为什么使用except: pass积木不好?是什么让它不好?是我pass出错还是我except出错了?


阅读 646

收藏
2020-02-10

共1个答案

一尘不染

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

我的解释要“长一点”,因此tl; dr可以细分为:

不要发现任何错误。始终指定你准备从哪些异常中恢复并仅捕获这些异常。
尽量避免传入除了blocks。除非明确要求,否则通常不是一个好兆头。
但是,让我们详细介绍一下:

不要发现任何错误
使用try块时,通常这样做是因为你知道有可能引发异常。这样,你还已经大概知道了哪些会中断,哪些异常会引发。在这种情况下,你会捕获异常,因为你可以从中积极地恢复过来。这意味着你已经为例外做好了准备,并有一些替代计划,在发生这种例外的情况下你将遵循该计划。

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

但是,如果我们要抓住一切,那么除了准备好从中恢复的异常之外,还有机会获得我们没有想到的异常,而我们确实无法从中恢复。或不应从中恢复。

让我们以上面的配置文件示例为例。如果文件丢失,我们将应用默认配置,并可能在以后决定自动保存配置(因此下次该文件存在)。现在想象我们得到一个IsADirectoryError或一个PermissionError代替。在这种情况下,我们可能不想继续。我们仍然可以应用默认配置,但是以后将无法保存文件。而且用户可能也打算具有自定义配置,因此可能不希望使用默认值。因此,我们希望立即将其告知用户,并且可能也中止程序执行。但这不是我们想要在某些小代码部分的深处做的事情;这在应用程序级别上很重要,因此应该在顶部进行处理-因此让异常冒出来。

Python 2习惯用法文档中还提到了另一个简单的示例。此处,代码中存在一个简单的错字,导致其中断。因为我们正在捕获每个异常,所以我们也捕获了NameErrorsSyntaxErrors。两者都是编程时我们所有人都会遇到的错误。两者都是我们在交付代码时绝对不希望包含的错误。但是,因为我们也抓住了它们,所以我们甚至都不知道它们在那里发生,并且失去了正确调试它的任何帮助。

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

无论如何,你几乎不可能为代码中的一小部分做好一切准备,因此,实际上,你应该只捕获准备好的那些异常。有人建议至少要抓住Exception它,因为它不会包含类似的内容,SystemExitKeyboardInterrupt这种情况在设计上是要终止你的应用程序的,但是我认为这仍然过于具体。我个人只在一个地方接受捕捞活动,Exception或者在任何地方异常,并且在单个全局应用程序级异常处理程序中,该异常处理程序的唯一目的是记录我们没有准备好的任何异常。这样,我们仍然可以保留有关意外异常的尽可能多的信息,然后我们可以使用这些信息扩展代码以显式处理这些异常(如果可以从异常中恢复),或者在发生错误的情况下创建测试用例以确保它不会再发生。但是,当然,只有当我们只捕获到我们已经期望的异常时,这才起作用,所以我们没想到的异常自然会冒出来。

尽量避免传入除了块

当显式地捕获少量特定异常时,在许多情况下,只要不执行任何操作就可以了。在这种情况下,拥有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后,你不太可能像往常一样继续)。只是传递信息不仅可以使应用程序保持一定的生命(当然,这取决于你捕获的位置),而且还会丢弃所有信息,从而无法发现错误-如果你不是发现错误的人,则尤其如此。

因此,底线是:仅捕获你真正期望并准备从中恢复的异常;其他所有问题都可能是你应该纠正的错误,或者你没有准备好应对。如果你真的不需要对异常进行处理,则传递特定的异常很好。在所有其他情况下,这只是推定和懒惰的标志。你肯定想解决该问题。

2020-02-10