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


324

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

try:
    something
except:
    pass

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


1
至少,我建议您将其记录下来,以便您知道自己正在忽略的问题。logging在DEBUG级别上使用该模块可避免它们在生产中流出,而在开发中保持可用。
pcurry

Answers:


345

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

我的解释是“更长”的时间,所以tl; dr可以细分为:

  1. 不要发现任何错误。始终指定您准备从中恢复的异常,并且仅捕获这些异常。
  2. 尽量避免传入除了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后,您不太可能像往常一样继续操作)。只是传递信息不仅可以使应用程序保持一定的生命力(当然,还取决于您捕获的位置),而且还会丢弃所有信息,从而无法发现错误-如果您不是发现错误的人,则尤其如此。


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


1
“您至少要记录该错误,还可能重新引发该错误以仍然终止应用程序”。您能演示如何“引发”异常以使其即使在捕捉到异常后仍继续冒泡吗?对于我来说,添加一些自定义错误消息似乎很有用,同时仍然让异常迫使应用程序退出。
加布里埃尔·斯台普斯

1
这有助于弄清楚:他们使用毯子except,但随后raise不带任何参数地调用以继续使异常冒泡,从而终止应用程序。我喜欢:ianbicking.org/blog/2007/09/re-raising-exceptions.html。关于不使用毯子的规则似乎是一个坚实的例外except
加布里埃尔·斯台普斯

1
@GabrielStaples是的,可以使用抛出异常raise。通常,您通常只在应用程序中的几个位置执行此操作,以记录异常。

很好,请避免传入除块以外的内容。我想说的是,做任何似乎更可理解的事情,尤其是对其他人而言。再获得另一组python眼睛,以检查您的代码,看看他们是否对代码块提出了疑问。可读性是关键。
radtek

262

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

这太多了。在您的脑海中,您正在思考“我想忽略此网络错误”。如果出乎意料的地方出了问题,那么您的代码将以无人能及的方式以无法预测的方式静默继续并中断。

这就是为什么您应该将自己限制为仅忽略某些错误,而让其余错误通过。


75

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

try:
    something
except:
    pass

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


51

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

为什么这样不好?

try:
    something
except:
    pass

这会捕获所有可能的异常,包括GeneratorExitKeyboardInterruptSystemExit-这是您可能不打算捕获的异常。和赶上一样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

这样,您将无法something使用Ctrl-C 中断您的代码块。您的程序将忽略try代码块内的所有可能的Exception 。

这是另一个具有相同不良行为的示例:

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. 

另一个示例的进一步说明

您之所以这样做,是因为您一直在爬网并说a UnicodeError,但是由于使用了最广泛的Exception catch,您的代码(可能有其他基本缺陷)将尝试运行至完成,浪费带宽,处理时间,设备的磨损,内存不足,收集垃圾数据等。

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

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


31
>>> import this

提姆·彼得斯(Tim Peters)撰写的《 Python之禅》

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

所以,这是我的看法。每当发现错误时,都应该采取措施进行处理,即将其写入日志文件或其他内容。至少,它通知您以前曾经有错误。


64
-1来自权威的争论实际上并不能解释任何事情。授权可能是错误的。
Izkata 2014年

23
@Izkata所写的内容,并且在其下一行,相同的权限写为:“除非明确地保持沉默”,这恰好是:pass所做的。
Ofri Raviv

13
@OfriRaviv不,错误不是隐式传递吗?明确地,将需要命名应该静默传递的错误,即,明确指出它。除了:pass以外,这不是什么。
Chelonian 2014年

24

您至少except Exception:应避免捕获诸如SystemExit或的系统异常KeyboardInterrupt。这里是文档链接

通常,应明确定义要捕获的异常,以避免捕获不需要的异常。您应该知道忽略了哪些异常。


13

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

  • 显式胜于隐式
  • 错误绝不能默默传递

这意味着您故意使错误静默地通过。而且,您不知道确切发生了哪个错误,因为except: pass它将捕获任何异常。

其次,如果我们试图从Python的Zen中抽象出来,并以理智的眼光说话,您应该知道,使用except:pass会使您在系统中没有知识和控制力。经验法则是在发生错误时引发异常,并采取适当的措施。如果您事先不知道该怎么做,请至少将错误记录在某个地方(并最好重新引发该异常):

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

但是,通常,如果您尝试捕获任何异常,则可能是在做错什么!


2
...除非明确静音,否则OP就是这种情况。
Hyperboreus 2014年

我想知道你的解决方案。实际上,当实际上什么都不需要做时,我只列出其中的错误,并进行注释和编写日志。然后就过去了。
Booster 2014年

2
@Hyperboreus,我不认为捕获所有错误会显式地消除它们,即,您甚至不知道您捕获了什么。
亚历山大·朱可夫

13
“因为有人说”实际上不是对“为什么”的答案。题。
塞巴斯蒂安·内格拉斯祖斯

12

except:pass构造实质上使在代码中涵盖的代码出现时出现的所有异常情况保持沉默。try:在运行块中。

造成这种不良习惯的原因在于,通常这并不是您真正想要的。更常见的情况是,您想沉默一些特定的情况,并且except:pass这实在是一种过时的工具。它可以完成工作,但也可以掩盖您可能未曾预料到的其他错误情况,但很可能希望以其他方式处理。

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


12

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

(#2)- 它使您的代码难以被他人阅读和理解。如果在尝试读取文件时捕获到FileNotFoundException,那么对于另一个开发人员而言,“ catch”块应具有的功能非常明显。如果未指定异常,则需要附加注释以说明该块应执行的操作。

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


12

那么,此代码产生什么输出?

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


5
嗯,感谢“纠正”此问题的人,但请不要这样做-从“面试问题”的意义上来说,这是故意的错误。它第一次出现时可能会更微妙-尝试一下。我的观点是,挤压“所有”异常(尤其是在Python中)使调试变得困难,即使只是一小段代码也是如此。
伊恩·哈维2014年

11

通常,您可以将任何错误/异常分为以下三种类别之一

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

  • 骨头:您自己的错误,很可能是由于疏忽,错误或编程错误所致。您应该修复该错误。同样,您当然应该不忽略并继续。

  • 外生的:在特殊情况下(例如找不到文件连接终止),您可能会遇到这些错误。您应该明确地处理这些错误,并且仅处理这些错误。

在任何情况下,except: pass都只会使程序处于未知状态,在这种状态下可能会造成更大的破坏。


6

简而言之,如果引发异常或错误,则说明存在问题。可能不是很不对劲,但是仅仅为了使用goto语句而创建,抛出和捕获错误和异常并不是一个好主意,而且很少这样做。99%的时间,某处出现问题。

需要解决的问题。就像生活中的情况一样,在编程中,如果您只是将问题搁置一旁并尝试忽略它们,那么它们就不会多次自行消失。相反,它们变得更大并成倍增加。为防止问题在您身上蔓延并进一步打击您,您可以1)消除它,然后清理残局,或者2)遏制它,然后清理残局。

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

在极少数情况下,问题是如此的微小,琐碎,并且-除了需要try ... catch块之外- 自包含的,以至于事后确实没有任何需要清理的地方。在这些情况下,这些最佳做法不一定适用。以我的经验,这通常意味着代码所做的任何事情基本上都是小巧的和可忽略的,而重试尝试或特殊消息之类的东西既不值得其复杂性也不值得其坚持下去。

在我公司,规则是几乎总是在某个陷阱中做某事,如果您什么都不做,那么您必须始终以非常充分的理由发表评论。要做任何事情时,您绝不能通过或留空捕获块。


6

在我看来,错误是有原因出现的,我的声音很愚蠢,但这就是事实。良好的编程仅在必须处理错误时才会引发错误。另外,正如我前段时间所读到的,“ 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

因此,即使是多个带有通过语句的except-block也会导致代码,其代码可处理特殊类型的异常。


4

到目前为止提出的所有评论均有效。在可能的情况下,您需要指定要忽略的异常。在可能的情况下,您需要分析导致异常的原因,只忽略您要忽略的内容,而不要忽略其余的内容。如果异常导致应用程序“严重崩溃”,那么就这样吧,因为比起掩盖曾经发生过的问题,了解意外事件在发生时更为重要。

综上所述,不要以任何编程实践为重。真傻 总是有时间和地点进行忽略所有例外的阻止。

愚蠢至高无上的另一个例子是goto运算符的用法。当我在学校的时候,我们的教授教我们goto操作员,只是提起您永远不要使用它。不要相信有人告诉您xyz永远不要使用,并且在任何情况下都不会有用。总有。


1
“ goto”情况在样式上是有见地的,而“ except:pass”通常在事实上是错误的。它假定如果有人要“杀死-TERM”您的过程,那么它应该忽略它。至少这是不良行为。
2014年

1
@Score_Under在某些情况下适合使用。例如,当您正在调用的功能是补充的,来源/作者未知时,不会影响核心功能,但是如果崩溃可能会引起麻烦。我确实意识到您会认为应该对这些调用进行适当的研究和分析,但是在现实生活中,这并非总是可能的。
galets

不过,如果我想终止您的进程,kill -9应该不是唯一可靠的选择。
2014年

2

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


2

由于尚未提及,因此使用更好的样式contextlib.suppress

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.