关于python:为什么”except:pass”是一个糟糕的编程实践?

Why is “except: pass” a bad programming practice?

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

1
2
3
4
try:
    something
except:
    pass

为什么使用except: pass块不好?是什么让事情变得糟糕?我是否有错误或我有错误?


正如您正确猜测的那样,它有两个方面:通过在except后面指定no exception type来捕获任何错误,并且简单地传递它而不采取任何操作。好的。

我的解释"有点长",所以tl;dr它可以分解为:好的。

  • 不要捕获任何错误。始终指定要从中恢复的异常,并且只捕获这些异常。
  • 尽量避免通过除街区外的其他街区。除非明确要求,否则这通常不是一个好迹象。
  • 但是,让我们详细分析一下:好的。不捕捉任何错误

    当使用try块时,通常会这样做,因为您知道有可能抛出异常。因此,您也已经大致了解了什么可以打破,什么可以抛出异常。在这种情况下,您会捕获一个异常,因为您可以从中积极地恢复过来。这意味着您已经为异常做好了准备,并且有了一些备用计划,在发生异常时您将遵循这些计划。好的。

    例如,当您要求用户输入一个数字时,可以使用int()转换输入,这可能会引发ValueError。你只需让用户再试一次就可以很容易地恢复它,因此捕获ValueError并再次提示用户是一个合适的计划。另一个例子是,如果您想从一个文件中读取一些配置,而该文件恰好不存在。因为它是一个配置文件,所以您可能有一些默认配置作为回退,因此该文件不是完全必需的。因此,捕获一个FileNotFoundError并简单地应用默认配置将是一个很好的计划。现在,在这两种情况下,我们都有一个非常具体的例外,我们期望它,并有一个同样具体的计划来从中恢复。因此,在每种情况下,我们都明确地仅限于cx1〔0〕那个特定的例外。好的。

    然而,如果我们要抓住每一件事,那么除了那些我们准备从中恢复的异常之外,还有一个机会就是我们得到了我们没有预料到的异常,并且我们确实无法从中恢复;或者不应该从中恢复。好的。

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

    另一个简单的例子也在python 2习语文档中提到。这里,代码中存在一个简单的打字错误,导致代码中断。因为我们捕捉到了每一个异常,所以我们也捕捉到了NameErrorSyntaxError。这两个错误都是在编程时发生在我们身上的;这两个错误都是我们在发送代码时绝对不想包括的错误。但是因为我们也捕捉到了这些,所以我们甚至不知道它们发生在那里,并且失去了正确调试它的任何帮助。好的。

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

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

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

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

    1
    2
    3
    4
    5
    6
    def askForNumber ():
        while True:
            try:
                return int(input('Please enter a number: '))
            except ValueError:
                pass

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

    不过,在许多其他情况下,仅仅传入一个except是一个信号,表明我们没有真正准备好应对我们正在捕获的异常情况。除非这些例外很简单(如ValueErrorTypeError),而且我们可以通过的原因很明显,否则尽量避免仅仅通过。如果真的没有什么要做的(并且您完全确定),那么考虑添加一个注释来说明为什么会是这样;否则,扩展except块以实际包含一些恢复代码。好的。except: pass

    但最严重的罪犯是两者的结合。这意味着我们愿意抓住任何错误,尽管我们完全没有准备,我们也没有做任何事情。您至少要记录错误,也可能重新发出它以终止应用程序(在发生内存错误后,您不太可能像正常一样继续)。只是传递消息不仅可以使应用程序保持一定的活动状态(当然,这取决于您在哪里捕获的信息),而且还可以丢弃所有信息,从而使您不可能发现错误,如果您不是发现错误的人,那么这一点尤其正确。好的。

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


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

    这太过分了。在你的头脑中,你在想"我想忽略这个网络错误"。如果发生意外错误,那么您的代码将以完全不可预知的方式静默地继续并中断,没有人可以调试它。

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


    从字面上执行伪代码甚至不会产生任何错误:

    1
    2
    3
    4
    try:
        something
    except:
        pass

    就好像它是一段完全有效的代码,而不是抛出一个NameError。我希望这不是你想要的。


    Why is"except: pass" a bad programming practice?

    Why is this bad?

    1
    2
    3
    4
    try:
        something
    except:
        pass

    这捕获了所有可能的异常,包括GeneratorExitKeyboardInterruptSystemExit,这可能是您不打算捕获的异常。这和抓BaseException是一样的。

    1
    2
    3
    4
    try:
        something
    except BaseException:
        pass

    旧版本的文档说:

    Since every error in Python raises an exception, using except: can make many programming errors look like runtime problems, which hinders the debugging process.

    python异常层次结构

    如果捕获一个父异常类,那么也将捕获其所有子类。只捕获准备处理的异常会更优雅。

    下面是python 3异常层次结构-您真的想全部捕获它们吗?:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    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

    不要这样做

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

    1
    2
    3
    4
    try:
        something
    except: # don't just do a bare except!
        pass

    然后,您将无法用ctrl-c中断您的something块。您的程序将忽略try代码块内的所有可能的异常。

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

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

    相反,尝试只捕获您知道要查找的特定异常。例如,如果知道转换时可能会出现值错误:

    1
    2
    3
    4
    5
    6
    7
    8
    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.

    用另一个例子进一步解释

    你这样做可能是因为你一直在网上抓取,比如说,一个UnicodeError,但是因为你使用了最广泛的异常抓取,你的代码,可能还有其他的基本缺陷,会试图运行到完成,浪费带宽,处理时间,磨损和损坏你的设备,耗尽内存,收集垃圾数据。ATA等。

    如果其他人要求您完成,以便他们可以依赖于您的代码,我理解您必须处理所有事情。但是,如果您愿意在开发过程中大声失败,那么您将有机会纠正可能只是间歇性出现的问题,但这将是长期代价高昂的错误。

    通过更精确的错误处理,您的代码可以更健壮。


    1
    >>> import this

    The Zen of Python, by Tim Peters

    Beautiful is better than ugly.
    Explicit is better than implicit.
    Simple is better than complex.
    Complex is better than complicated.
    Flat is better than nested.
    Sparse is better than dense.
    Readability counts.
    Special cases aren't special enough to break the rules.
    Although practicality beats purity.
    Errors should never pass silently.
    Unless explicitly silenced.
    In the face of ambiguity, refuse the temptation to guess.
    There should be one-- and preferably only one --obvious way to do it.
    Although that way may not be obvious at first unless you're Dutch.
    Now is better than never.
    Although never is often better than right now.
    If the implementation is hard to explain, it's a bad idea.
    If the implementation is easy to explain, it may be a good idea.
    Namespaces are one honking great idea -- let's do more of those!

    所以,我的意见是。每当你发现一个错误时,你应该做一些事情来处理它,即把它写在日志文件或其他什么文件中。至少,它会通知您曾经有一个错误。


    您应该至少使用except Exception:以避免捕获系统异常,如SystemExitKeyboardInterrupt。这是到文档的链接。

    一般来说,您应该明确定义要捕获的异常,以避免捕获不需要的异常。你应该知道你忽略了哪些例外。


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

    • 显式优于隐式
    • 错误永远不会悄悄地过去。

    它的意思是,你故意让你的错误安静地过去。此外,您不知道具体发生了哪个错误,因为except: pass将捕获任何异常。

    第二,如果我们试着从Python的禅宗中抽象出来,用公正的理智说话,你应该知道,使用except:pass会使你的系统失去知识和控制。经验法则是在发生错误时引发异常,并采取适当的措施。如果您事先不知道这些操作应该是什么,至少将错误记录在某个地方(最好重新引发异常):

    1
    2
    3
    4
    try:
        something
    except:
        logger.exception('Something happened')

    但是,通常,如果你试图抓住任何例外,你很可能是做错了什么!


    已经说明了1原因-它隐藏了您没有预料到的错误。

    (2)-这会使您的代码难以让其他人阅读和理解。如果您在尝试读取文件时捕获了fileNotFoundException,那么对于另一个开发人员来说,"catch"块应该具有什么功能是非常明显的。如果不指定异常,则需要附加注释来解释块应该做什么。

    (3)-它演示了懒惰的编程。如果使用通用的Try/Catch,则表明您不了解程序中可能出现的运行时错误,或者不知道在Python中可能出现哪些异常。捕捉一个特定的错误表明您了解了您的程序和Python抛出的错误范围。这更有可能使其他开发人员和代码评审人员相信您的工作。


    except:pass构造基本上消除了运行try:块中包含的代码时出现的所有异常情况。

    造成这种坏习惯的原因是它通常不是你真正想要的。更常见的是,一些特定的情况出现了,你想沉默,而except:pass是一个过于钝的工具。它将完成任务,但也将掩盖您可能没有预料到的其他错误条件,但可能非常希望以其他方式处理。

    在Python中,这一点尤为重要的是,根据这种语言的习惯用法,异常不一定是错误的。当然,它们通常是这样使用的,就像大多数语言一样。但是,特别是python偶尔会使用它们来实现一些代码任务的可选退出路径,这些代码任务实际上不是正常运行情况的一部分,但仍然知道会不时出现,在大多数情况下甚至可能出现。SystemExit已经作为一个旧的例子被提到,但是现在最常见的例子可能是StopIteration。以这种方式使用异常引起了很多争议,特别是当迭代器和生成器首次引入到Python中时,但最终这一想法盛行。


    那么,这个代码产生什么输出?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    fruits = [ 'apple', 'pear', 'carrot', 'banana' ]

    found = False
    try:
         for i in range(len(fruit)):
             if fruits[i] == 'apple':
                 found = true
    except:
         pass

    if found:
        print"Found an apple"
    else:
        print"No apples in list"

    现在假设try--except块是对复杂对象层次结构的数百行调用,它本身被调用在大型程序调用树的中间。当程序出错时,您从哪里开始查找?


    通常,您可以将任何错误/异常分为三类:

    • 致命的:不是你的错,你不能阻止他们,你不能从他们身上恢复。您当然不应该忽略它们并继续,让您的程序处于未知状态。只要让错误终止您的程序,您就无能为力了。

    • 愚蠢的:你自己的错误,很可能是由于疏忽、错误或编程错误。你应该修正这个错误。同样,你也不应该忽视和继续。

    • 外生的:您可以在异常情况下期望这些错误,例如找不到文件或连接终止。您应该显式地处理这些错误,并且只处理这些错误。

    在所有情况下,except: pass只会使您的程序处于未知状态,在这种状态下它会造成更多的损害。


    在我看来,错误是有原因出现的,我的声音听起来很愚蠢,但事实就是这样。好的编程只会在必须处理错误时引发错误。另外,正如我前一段时间读到的,"pass语句是一个显示代码将在稍后插入的语句",因此如果您想要一个空的except语句,请随意这样做,但是对于一个好的程序,会有一个部分丢失。因为你不能处理你应该拥有的东西。出现异常会使您有机会纠正输入数据或更改数据结构,使这些异常不会再次发生(但在大多数情况下(网络异常、一般输入异常)异常表明程序的下一个部分执行不好。例如,networkexception可以指示断开的网络连接,并且程序无法在下一个程序步骤中发送/接收数据。

    但是只对一个执行块使用pass块是有效的,因为在异常类型之间仍然存在差异,因此如果将所有异常块放在一个中,则它不是空的:

    1
    2
    3
    4
    5
    6
    7
    8
    try:
        #code here
    except Error1:
        #exception handle1

    except Error2:
        #exception handle2
    #and so on

    可以这样重写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    try:
        #code here
    except BaseException as e:
        if isinstance(e, Error1):
            #exception handle1

        elif isinstance(e, Error2):
            #exception handle2

        ...

        else:
            raise

    因此,即使多个带有pass语句的except块也可能导致代码,其结构处理特殊类型的异常。


    简单地说,如果抛出异常或错误,就会出现问题。这可能不是什么大错误,但是仅仅为了使用goto语句而创建、抛出和捕获错误和异常并不是一个好主意,而且很少这样做。99%的时候,某个地方出现了问题。

    问题需要处理。就像生活中,编程中一样,如果你把问题放在一边,试着忽略它们,它们不仅会自己消失很多次,反而会变得越来越大,越来越多。为了防止一个问题在你身上滋生,并再次沿着道路继续前进,你要么1)消除它,然后清理这些乱七八糟的东西,要么2)遏制它,然后清理这些乱七八糟的东西。

    只需忽略异常和错误,并像这样保留它们,就可以很好地体验内存泄漏、出色的数据库连接、不必要的文件权限锁等等。

    在极少数情况下,问题是如此的微小,琐碎,而且-除了需要一个尝试…捕获块-自给自足,以至于真正没有混乱需要清理后。只有在这些情况下,这种最佳实践并不一定适用。在我的经验中,这通常意味着无论代码在做什么,基本上都是琐碎的和可原谅的,并且像重试尝试或特殊消息之类的东西既不值得复杂也不值得保持线程。

    在我的公司,规则是几乎总是在一个捕获块中做一些事情,如果你不做任何事情,那么你必须总是用一个非常好的理由来发表评论。当有任何事情要做时,您决不能通过或离开空的catch块。


    迄今为止提出的所有意见都是有效的。在可能的情况下,您需要指定要忽略的异常。在可能的情况下,您需要分析导致异常的原因,并且只忽略您要忽略的内容,而不忽略其余的内容。如果异常导致应用程序"惊人地崩溃",那么就这样吧,因为知道发生意外时发生的情况比隐瞒问题曾经发生更重要。

    尽管如此,不要把任何编程实践作为首要任务。这太愚蠢了。总是有时间和地点可以忽略所有异常块。

    另一个白痴派拉蒙的例子是使用goto操作符。我在学校的时候,我们的教授教我们一个(0)接线员,只是说永远不要用它。不要相信有人告诉你XYZ永远不应该被使用,而且在它有用的时候也不会有场景。总是有的。


    ?处理错误在编程中非常重要。您确实需要向用户显示出了什么问题。在极少数情况下,您可以忽略错误。这是非常糟糕的编程实践。


    由于还没有提到,所以最好使用contextlib.suppress

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

    请注意,在提供的示例中,无论异常是否发生,程序状态都保持不变。也就是说,somefile.tmp总是不存在。