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

try:
    something
except:
    pass

为什么使用except: pass块不好?是什么让它变得糟糕?是我传递了一个错误还是我排除了任何错误?


当前回答

我正在构建一个将在数据中心运行的应用程序。它不应该生成任何错误或引发任何异常。我的数据中心有一个网络监控系统,其中包括一个SNMP trap接收器。

try:
    main()
except as e:
    log(str(e))
    send_snmp_trap(str(e))
    raise

但是这个加薪不会有任何效果因为它是和任何可能剩下的堆栈的底部。

顺便说一句,这不是万能的灵丹妙药。有一些例外情况是无法被发现的。SNMP不能保证传输。YMMV。

其他回答

因为它还没有被提及,所以使用contextlib.suppress是更好的风格:

with suppress(FileNotFoundError):
    os.remove('somefile.tmp')

在本例中,somefile.tmp在执行此代码块后将不存在,而不会引发任何异常(除了FileNotFoundError,它被抑制)。

为什么“except: pass”是一种糟糕的编程实践? 这为什么不好呢? 试一试: 某物 除了: 通过

这将捕获所有可能的异常,包括GeneratorExit、KeyboardInterrupt和SystemExit——这些异常可能是您不打算捕获的。这和捕获BaseException是一样的。

try:
    something
except BaseException:
    pass

旧版本的文档说:

由于Python中的每个错误都会引发异常,使用except:可以使许多编程错误看起来像运行时问题,从而阻碍调试过程。

Python异常层次结构

如果您捕获了一个父异常类,那么您也捕获了它们的所有子类。只捕获准备处理的异常要优雅得多。

下面是Python 3的异常层次结构——你真的想把它们都捕捉到吗?:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
           +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

不要这样做

如果你使用这种形式的异常处理:

try:
    something
except: # don't just do a bare except!
    pass

然后你就不能用Ctrl-C来中断你的something块了。您的程序将忽略try代码块中所有可能的异常。

下面是另一个同样有不良行为的例子:

except BaseException as e: # don't do this either - same as bare!
    logging.info(e)

相反,尝试只捕获您知道正在寻找的特定异常。例如,如果你知道你可能会在一个转换中得到一个值错误:

try:
    foo = operation_that_includes_int(foo)
except ValueError as e:
    if fatal_condition(): # You can raise the exception if it's bad,
        logging.info(e)   # but if it's fatal every time,
        raise             # you probably should just not catch it.
    else:                 # Only catch exceptions you are prepared to handle.
        foo = 0           # Here we simply assign foo to 0 and continue. 

用另一个例子进一步解释

您可能会这样做,因为您已经进行了web抓取并得到了一个UnicodeError,但因为您使用了最广泛的异常捕获,您的代码可能有其他基本缺陷,将试图运行到完成,浪费带宽,处理时间,磨损您的设备,耗尽内存,收集垃圾数据等。

如果其他人要求你完成,这样他们就可以依赖你的代码,我理解那种被迫处理所有事情的感觉。但如果你愿意在开发过程中遭遇失败,你就有机会纠正那些偶尔出现的问题,但这将是代价高昂的长期错误。

有了更精确的错误处理,代码就会更加健壮。

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

我的解释“有点”长,所以可以分解成这样:

不要捕捉任何错误。始终指定准备从哪些异常中恢复,并只捕获这些异常。 尽量避免传入除块。除非有明确的愿望,否则这通常不是一个好迹象。

但让我们来详细谈谈:

不要捕捉任何错误

在使用try块时,通常会这样做,因为您知道有可能抛出异常。因此,您也已经大致了解了可以破坏什么以及可以抛出什么异常。在这种情况下,您可以捕获异常,因为您可以积极地从中恢复。这意味着您已经为异常做好了准备,并有了一些可选的计划,以便在出现异常的情况下执行。

For example, when you ask for the user to input a number, you can convert the input using int() which might raise a ValueError. You can easily recover that by simply asking the user to try it again, so catching the ValueError and prompting the user again would be an appropriate plan. A different example would be if you want to read some configuration from a file, and that file happens to not exist. Because it is a configuration file, you might have some default configuration as a fallback, so the file is not exactly necessary. So catching a FileNotFoundError and simply applying the default configuration would be a good plan here. Now in both these cases, we have a very specific exception we expect and have an equally specific plan to recover from it. As such, in each case, we explicitly only except that certain exception.

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

Let’s take the configuration file example from above. In case of a missing file, we just applied our default configuration and might decide at a later point to automatically save the configuration (so next time, the file exists). Now imagine we get a IsADirectoryError, or a PermissionError instead. In such cases, we probably do not want to continue; we could still apply our default configuration, but we later won’t be able to save the file. And it’s likely that the user meant to have a custom configuration too, so using the default values is likely not desired. So we would want to tell the user about it immediately, and probably abort the program execution too. But that’s not something we want to do somewhere deep within some small code part; this is something of application-level importance, so it should be handled at the top—so let the exception bubble up.

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

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

In any case, it’s very unlikely that you are prepared for everything in a small-scale part of the code, so that’s really where you should only catch those exceptions you are prepared for. Some people suggest to at least catch Exception as it won’t include things like SystemExit and KeyboardInterrupt which by design are to terminate your application, but I would argue that this is still far too unspecific. There is only one place where I personally accept catching Exception or just any exception, and that is in a single global application-level exception handler which has the single purpose to log any exception we were not prepared for. That way, we can still retain as much information about unexpected exceptions, which we then can use to extend our code to handle those explicitly (if we can recover from them) or—in case of a bug—to create test cases to make sure it won’t happen again. But of course, that only works if we only ever caught those exceptions we were already expecting, so the ones we didn’t expect will naturally bubble up.

尽量避免传入除块

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

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

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

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

不过,在许多其他情况下,仅仅传入一个except是一个迹象,表明我们没有真正为正在捕获的异常做好准备。除非这些异常很简单(如ValueError或TypeError),并且可以传递的原因很明显,否则尽量避免直接传递。如果真的没什么可做的(并且你绝对确定),那么考虑添加一个注释为什么会这样;否则,展开except块以实际包含一些恢复代码。

除了:通过

The worst offender though is the combination of both. This means that we are willingly catching any error although we are absolutely not prepared for it and we also don’t do anything about it. You at least want to log the error and also likely reraise it to still terminate the application (it’s unlikely you can continue like normal after a MemoryError). Just passing though will not only keep the application somewhat alive (depending on where you catch of course), but also throw away all the information, making it impossible to discover the error—which is especially true if you are not the one discovering it.


因此,底线是:只捕获您真正期望并准备从中恢复的异常;所有其他的可能要么是你应该改正的错误,要么是你根本没有准备好的事情。如果您真的不需要对特定的异常做什么,那么传递特定的异常是可以的。在所有其他情况下,这只是一种傲慢和懒惰的表现。你肯定想解决这个问题。

首先,它违背了Python的两个禅宗原则:

显性比隐性好 错误绝不能悄无声息地过去

它的意思是,你故意让你的错误悄无声息地过去。此外,您不知道究竟发生了哪个错误,因为except: pass将捕获任何异常。

其次,如果我们试图从Python的禅意中抽象出来,而只是从理智的角度来说话,你应该知道,使用except:pass会让你在系统中失去知识和控制。经验法则是,如果发生错误,就引发异常,并采取适当的操作。如果你事先不知道这些操作应该是什么,至少在某个地方记录错误(最好重新引发异常):

try:
    something
except:
    logger.exception('Something happened')

但是,通常情况下,如果您试图捕获任何异常,那么您可能正在做错误的事情!

如果这是糟糕的做法,“通过”就不会是一个选项。 如果你有一个资产,从许多地方接收信息IE一个表单或userInput,它就会派上用场。

variable = False
try:
    if request.form['variable'] == '1':
       variable = True
except:
    pass