【问题标题】:Why is "except: pass" a bad programming practice?为什么“除了:通过”是一种不好的编程习惯?
【发布时间】:2014-02-28 11:19:36
【问题描述】:

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

try:
    something
except:
    pass

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

【问题讨论】:

标签: python exception error-handling try-catch


【解决方案1】:

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

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

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

但让我们详细介绍一下:

不要发现任何错误

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

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

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

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

Python 2 idioms 文档中还提到了另一个简单的例子。在这里,代码中存在一个简单的错字导致它中断。因为我们正在捕获每一个异常,我们也捕获了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 之后您不太可能像往常一样继续)。只是传递不仅会使应用程序保持一定的活力(当然取决于您捕获的位置),而且还会丢弃所有信息,从而无法发现错误 - 如果您不是发现错误的人,尤其如此。


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

【讨论】:

  • “您至少想要记录错误并且还可能重新引发它以仍然终止应用程序”。你能演示如何“重新引发”一个异常,让它在捕获它之后继续冒泡吗?这对我来说似乎很有用,可以添加一些自定义错误消息,同时仍然让异常强制应用程序退出。
  • 这有助于澄清:他们使用毯子except,但随后不带参数地调用raise 继续让异常冒泡,终止应用程序。我喜欢它:ianbicking.org/blog/2007/09/re-raising-exceptions.html。看起来像是关于不使用毯子 except 的规则的一个可靠例外。
  • @GabrielStaples 是的,可以使用raise 重新抛出捕获的异常。不过,您通常只会在应用程序中的几个位置执行此操作以记录异常。
  • 这很好,避免传入 except 块。我会说,做任何看起来更容易理解的事情,尤其是对其他人来说。让第二组 python 眼睛来审查你的代码,看看他们是否质疑这个块。可读性是关键。
  • @poke 你可以使用raise Exception() from e
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

    执行你的伪代码字面意思甚至不会给出任何错误:

    try:
        something
    except:
        pass
    

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

    【讨论】:

      【解决方案4】:

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

      为什么会这样?

      try:
          something
      except:
          pass
      

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

      try:
          something
      except BaseException:
          pass
      

      Olderdocumentation say 版本:

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

      Python 异常层次结构

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

      这里是 Python 3 exception hierarchy - 你真的想抓住他们吗?:

      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. 
      

      再举例说明

      您可能会这样做是因为您一直在进行网络抓取并得到说,UnicodeError,但因为您使用了最广泛的异常捕获,您的代码可能有其他基本缺陷,将尝试运行完成、浪费带宽、处理时间、设备磨损、内存不足、收集垃圾数据等。

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

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

      【讨论】:

        【解决方案5】:
        >>> import this
        

        Python 之禅,作者 Tim Peters

        美丽胜于丑陋。
        显式优于隐式。
        简单胜于复杂。
        复杂胜于复杂。
        平面比嵌套好。
        稀疏优于密集。
        可读性很重要。
        特殊情况不足以打破规则。
        虽然实用胜于纯粹。
        错误绝不应该默默地过去。
        除非明确静音。
        面对模棱两可,拒绝猜测的诱惑。
        应该有一种——最好只有一种——明显的方法。
        虽然这种方式一开始可能并不明显,除非你是荷兰人。
        现在总比没有好。
        虽然现在从来没有比现在更好正确
        如果实现难以解释,那就是个坏主意。
        如果实现很容易解释,那可能是个好主意。
        命名空间是一个很棒的主意——让我们做更多的事情!

        所以,这是我的看法。每当你发现一个错误时,你应该做一些事情来处理它,即将它写入日志文件或其他东西。至少,它会通知您曾经有过错误。

        【讨论】:

        • -1 权威的论点实际上并没有解释任何东西。权威可能是错误的。
        • @Izkata 写了什么,并且,在它下面的一行,同一权威写道:“除非明确沉默”,这正是 except: pass 所做的。
        • @OfriRaviv 不,错误不是隐式传递吗?明确地需要命名应该静默传递的错误,即明确说明。这不是 except:pass 所做的。
        【解决方案6】:

        您应该至少使用except Exception: 以避免捕获系统异常,例如SystemExitKeyboardInterrupt。这是 link 给文档。

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

        【讨论】:

          【解决方案7】:

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

          (#2) - 它使其他人难以阅读和理解您的代码。 如果您在尝试读取文件时捕获了 FileNotFoundException,那么对于其他开发人员来说很明显'catch' 块应该具有的功能。如果你没有指定异常,那么你需要额外的注释来解释这个块应该做什么。

          (#3) - 它演示了惰性编程。如果您使用泛型 try/catch,则表明您不了解程序中可能存在的运行时错误,或者您不知道 Python 中可能出现哪些异常。捕获特定错误表明您既了解您的程序,也了解 Python 抛出的错误范围。这更有可能使其他开发人员和代码审查员信任您的工作。

          【讨论】:

            【解决方案8】:

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

            • 显式优于隐式
            • 错误绝不应无声无息地过去

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

            其次,如果我们试图从 Python 的禅意中抽象出来,并且只谈理智,你应该知道,使用 except:pass 会让你的没有知识和控制系统。经验法则是在发生错误时引发异常,并采取适当的措施。如果您事先不知道这些应该是什么操作,至少在某处记录错误(并更好地重新引发异常):

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

            但是,通常,如果你试图捕捉任何异常,你可能做错了什么!

            【讨论】:

            • ...除非明确静音,OP 就是这种情况。
            • 我想知道你的解决方案。事实上,当真的什么都不需要做的时候,我只是在except中列出错误并制作cmet并写入日志。然后就过去了。
            • @Hyperboreus,我不认为,捕获所有和任何错误都会明确地使它们静音,也就是说,你甚至不知道,你捕获了什么。
            • “因为有人这么说”并不是真正回答“为什么?”问题。
            【解决方案9】:

            except:pass 构造实质上可以消除在运行 try: 块中所涵盖的代码时出现的所有异常情况。

            造成这种不良做法的原因在于它通常不是你真正想要的。更多的时候,一些特定的情况即将出现,你想保持沉默,而 except:pass 太过分了一种钝器。它会完成工作,但也会掩盖您可能没有预料到但很可能希望以其他方式处理的其他错误情况。

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

            【讨论】:

              【解决方案10】:

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

              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 块是对复杂对象层次结构的数百行调用,并且它本身在大型程序的调用树的中间被调用。当程序出错时,你从哪里开始寻找?

              【讨论】:

              • 呃,感谢“纠正”这个问题的人,但请不要 - 这是故意错误的,在“面试问题”的意义上。它第一次出现可能更微妙 - 试试看。我的观点是,压缩“所有”异常,尤其是在 Python 中,会使调试变得困难,即使在几十行代码中也是如此。
              【解决方案11】:

              一般来说,您可以将任何错误/异常归类为three categories 之一:

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

              • Boneheaded:您自己的错,很可能是由于疏忽、错误或编程错误。你应该修复这个错误。同样,您绝对不应该忽略并继续。

              • 外部:您可能会在异常情况下出现这些错误,例如找不到文件连接终止。您应该明确处理这些错误,并且只处理这些错误。

              在所有情况下,except: pass 只会让您的程序处于未知状态,这可能会造成更大的损害。

              【讨论】:

                【解决方案12】:

                到目前为止提出的所有 cmets 都是有效的。在可能的情况下,您需要指定要忽略的确切异常。在可能的情况下,您需要分析导致异常的原因,并且只忽略您要忽略的内容,而不是其余部分。如果异常导致应用程序“严重崩溃”,那就这样吧,因为知道意外发生的时间比隐瞒问题曾经发生更重要。

                话虽如此,但不要将任何编程实践视为最重要的。这是愚蠢的。总是有时间和地点来做ignore-all-exceptions block。

                另一个愚蠢的例子是goto 运算符的使用。当我在学校的时候,我们的教授教我们goto 运算符只是提到你永远不要使用它。不要相信那些告诉你 xyz 永远不应该被使用的人,并且不可能有它有用的场景。总会有的。

                【讨论】:

                • “goto”案例是风格和意见问题,而“except: pass”通常实际上是错误的。它假设如果有人在那个时候“杀死 -TERM”你的进程,那么它应该忽略它。至少这是不好的行为。
                • @Score_Under 但在某些情况下这是适合使用的。例如,当您调用的函数是补充的,来源/作者未知时,不会影响核心功能,但如果崩溃可能会导致麻烦。我确实意识到您会争辩说应该对此类调用进行适当的研究和分析,但在现实生活中并不总是可行的。
                • 不过,如果我想终止你的进程,kill -9 不应该是唯一可靠的选择。
                • 除此之外还有另一面:通过评估,也就是try下的那一行代码成功与否的重要性,与整体代码流成功的重要性。
                【解决方案13】:

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

                需要处理的问题。就像生活中的情况一样,在编程中,如果您只是不理会问题并尝试忽略它们,它们不会很多时候自行消失;相反,它们变得更大并成倍增加。为防止问题在您身上蔓延并在未来再次出现,您可以 1) 消除它并事后清理混乱,或 2) 控制它并事后清理混乱。

                忽略异常和错误并让它们保持原样是体验内存泄漏、出色的数据库连接、不必要的文件权限锁定等的好方法。

                在极少数情况下,问题是如此微小、微不足道,而且 - 除了需要一个 try...catch 块 - 自包含,真的没有什么需要清理的然后。只有在这些情况下,这种最佳实践不一定适用。以我的经验,这通常意味着无论代码在做什么基本上都是琐碎的和可以放弃的,诸如重试尝试或特殊消息之类的东西既不值得复杂也不值得保持线程。

                在我的公司,规则是几乎总是在 catch 块中做某事,如果你什么都不做,那么你必须总是发表评论,说明为什么不这样做.当有任何事情要做时,你绝不能通过或留下一个空的 catch 块。

                【讨论】:

                  【解决方案14】:

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

                  但是只为一个执行块使用一个pass块是有效的,因为你仍然区分异常的类型,所以如果你把所有的异常块放在一个中,它就不是空的:

                  try:
                      #code here
                  except Error1:
                      #exception handle1
                  
                  except Error2:
                      #exception handle2
                  #and so on
                  

                  可以这样改写:

                  try:
                      #code here
                  except BaseException as e:
                      if isinstance(e, Error1):
                          #exception handle1
                  
                      elif isinstance(e, Error2):
                          #exception handle2
                  
                      ...
                  
                      else:
                          raise
                  

                  因此,即使是多个带有传递语句的异常块也可以生成代码,其结构处理特殊类型的异常。

                  【讨论】:

                    【解决方案15】:

                    既然还没提到,还是用contextlib.suppress比较好:

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

                    在这个例子中,somefile.tmp 将在这段代码执行后不存在而不引发任何异常(FileNotFoundError 除外,它被抑制)。

                    【讨论】:

                      【解决方案16】:

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

                      【讨论】:

                        【解决方案17】:

                        如果这是不好的做法,“通过”将不是一个选项。 如果您有一个资产可以从许多地方接收信息,例如表单或用户输入,它就会派上用场。

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

                        【讨论】:

                          【解决方案18】:

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

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

                          除了加注不会去任何地方,因为它是任何可能剩下的堆栈的底部。

                          顺便说一句,这无论如何都不是万能的灵丹妙药。有一些异常是无法捕获的。 SNMP 不保证交付。 YMMV。

                          【讨论】:

                            猜你喜欢
                            • 2014-03-01
                            • 2014-08-12
                            • 1970-01-01
                            • 2020-01-03
                            • 2012-04-23
                            相关资源
                            最近更新 更多