为什么“除了:传递”一个糟糕的编程习惯?

时间:2014-02-04 13:02:51

标签: python exception exception-handling error-handling try-catch

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

try:
    something
except:
    pass

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

18 个答案:

答案 0 :(得分:307)

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

我的解释是“有点”更长 - 所以tl; dr打破了这个:

  1. 请勿抓住任何错误。始终指定您准备从哪些例外中恢复并仅捕获这些例外。
  2. 尽量避免传入除块之外的其他内容。除非明确要求,否则这通常不是一个好兆头。
  3. 但是让我们详细说明一下:

    不要捕捉任何错误

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

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

    但是,如果我们要抓住所有,那么除了我们准备从中恢复的那些例外之外 - 我们还有机会获得我们没想到的异常,并且我们确实无法从中恢复;或者不应该从中恢复。

    让我们从上面获取配置文件示例。如果文件丢失,我们只应用我们的默认配置,并可能稍后决定自动保存配置(因此下次文件存在)。现在假设我们得到IsADirectoryErrorPermissionError。在这种情况下,我们可能不想继续;我们仍然可以应用我们的默认配置,但我们以后将无法保存该文件。并且用户可能也想要自定义配置,因此可能不需要使用默认值。所以我们想立即告诉用户它,并且可能也会中止程序执行。但这不是我们想要在某些小代码部分深处做的事情;这是应用程序级别的重要性,所以它应该在顶部处理 - 所以让异常冒泡。

    Python 2 idioms文档中还提到了另一个简单示例。这里,代码中存在一个简单的拼写错误,导致它破坏。因为我们正在捕获每个异常,所以我们也会抓住NameErrorsSyntaxErrors。两者都是编程时发生在我们身上的错误;这两个都是我们在发货时绝对不想包含的错误。但是因为我们也抓住了这些,我们甚至不知道它们发生在那里并且失去了正确调试它的任何帮助。

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

    在任何情况下,你都不太可能为代码的一小部分内容做好准备,所以你应该只抓住你准备好的那些例外。有些人建议至少抓住Exception,因为它不会包含SystemExitKeyboardInterrupt by design 来终止你的应用程序,但我会认为这仍然太不明确。只有一个地方我个人接受捕获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之后,您不太可能像正常一样继续)。只是通过它不仅会使应用程序保持活跃(取决于你当然捕获的地方),而且还丢弃所有信息,使得无法发现错误 - 如果你不是发现它的话,尤其如此。 / p>


    所以底线是:只抓住你真正期望的并准备从中恢复的例外;所有其他可能是你应该修复的错误,或者你不准备的事情。如果你真的不需要对它们做些什么,那么传递特定的例外是可以的。在所有其他情况下,这只是推定和懒惰的标志。你肯定想解决这个问题。

答案 1 :(得分:257)

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

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

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

答案 2 :(得分:71)

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

try:
    something
except:
    pass

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

答案 3 :(得分:46)

  

为什么“除了:传递”一个糟糕的编程习惯?

     

为什么这么糟糕?

try:
    something
except:
    pass

这会捕获所有可能的异常,包括GeneratorExitKeyboardInterruptSystemExit - 这些异常是您可能无意捕获的。它与捕捉BaseException相同。

try:
    something
except BaseException:
    pass
Older

documentation 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,但是因为你使用了最广泛的异常捕获,你的代码可能有其他根本缺陷,尝试运行完成,浪费带宽,处理时间,磨损设备,耗尽内存,收集垃圾数据等。

如果其他人要求您完成以便他们可以依赖您的代码,我理解被迫只是处理所有事情。但是如果你愿意随着你的发展而吵闹,你将有机会纠正可能只是间歇性地出现的问题,但那将是长期代价高昂的错误。

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

答案 4 :(得分:27)

>>> import this
  

Python的禅宗,蒂姆·彼得斯

     

美丽胜过丑陋   明确比隐含更好   简单比复杂更好   复杂比复杂更好   扁平比嵌套好。
  稀疏比密集更好   可读性很重要   特殊情况不足以打破规则   虽然实用性超过了纯度   错误永远不会无声地传递。
  除非明确沉默。
  面对模棱两可,拒绝猜测的诱惑   应该有一个 - 最好只有一个 - 明显的方式来做到这一点   虽然这种方式起初可能并不明显,除非你是荷兰人   现在总比没有好。
  虽然现在永远不会比正确更好   如果实施很难解释,那是个坏主意   如果实施很容易解释,那可能是个好主意   命名空间是一个很好的主意 - 让我们做更多的事情!

所以,这是我的意见。每当您发现错误时,您应该做一些事情来处理它,即将其写入日志文件或其他内容。至少,它告诉你曾经有过错误。

答案 5 :(得分:23)

您应该至少使用except Exception:来避免捕获SystemExitKeyboardInterrupt等系统异常。这是文档的link

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

答案 6 :(得分:12)

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

  • 明确胜于隐含
  • 错误永远不应以无声方式传递

这意味着,你是故意让你的错误无声地通过。此外,您不知道事件,确切发生了哪个错误,因为except: pass将捕获任何异常。

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

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

但是,通常情况下,如果您尝试捕获任何异常,那么您可能做错了什么!

答案 7 :(得分:12)

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

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

(#3) - 它演示了懒惰的编程。如果你使用通用的try / catch,它表示你不理解你的程序中可能的运行时错误,或者你不知道Python中可能有哪些例外。捕获特定错误表明您了解程序和Python抛出的错误范围。这更有可能使其他开发人员和代码审阅者信任您的工作。

答案 8 :(得分:11)

except:pass构造基本上使正在运行try:块中的代码时出现的任何和所有异常情况都无法使用。

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

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

答案 9 :(得分:11)

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

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

答案 10 :(得分:10)

通常,您可以在three categories之一

中对任何错误/异常进行分类
  • 致命:不是你的错,你无法阻止它们,你无法从中恢复。您当然不应该忽略它们并继续,并使您的程序处于未知状态。只是让错误终止你的程序,你无能为力。

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

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

在所有情况下,except: pass只会使您的程序处于未知状态,从而导致更多损坏。

答案 11 :(得分:5)

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

但是只对一个execption-block使用传递块是有效的,因为你仍然在异常类型之间区分,所以如果你把所有异常块都放在一个中,它就不是空的:

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

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

答案 12 :(得分:5)

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

需要解决的问题。就像在生活中一样,在编程中,如果你只是单独留下问题并试图忽略它们,它们不仅会自行消失很多次;相反,他们变得越来越大。为了防止问题在你身上蔓延并再次在路上再次打击,要么1)消除它并在之后清理乱七八糟,或者2)包含它并随后清理乱七八糟。

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

在极少数情况下,问题是如此微不足道,琐碎,而且 - 除了需要尝试...抓住阻止 - 自足,真的没有什么可以清理干净然后。这是最佳实践不一定适用的唯一场合。根据我的经验,这通常意味着无论代码在做什么都基本上是小事而且可以原谅,重试尝试或特殊消息之类的东西既不复杂也不值得坚持。

在我的公司,规则是几乎总是在catch块中执行某事,如果你什么都不做,那么你必须总是发表评论,其中有很好的理由为什么不。当有任何事情要做时,你绝不能传递或留下空的挡块。

答案 13 :(得分:4)

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

尽管如此,不要把任何编程习惯作为最重要的。这是愚蠢的。总是有时间和地点做忽略 - 所有例外的阻止。

愚蠢至上的另一个例子是使用goto运算符。当我在学校时,我们的教授告诉我们goto操作员只是提到你不能使用它,永远。不要相信人们告诉你xyz永远不应该被使用,并且当它有用时就不会出现这种情况。总是有。

答案 14 :(得分:2)

处理错误在编程中非常重要。您需要向用户显示出错的地方。在极少数情况下,您可以忽略错误。这是非常糟糕的编程习惯。

答案 15 :(得分:0)

由于尚未被提及,因此最好使用contextlib.suppress

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

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

答案 16 :(得分:0)

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

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

除了加薪不会走到任何地方,因为它和可能剩下的任何筹码的底部。

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

答案 17 :(得分:0)

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

 <div class="form-group row">
                        <label class="control-label col-md-3 col-sm-3 ">{{formUser.th_wali.label_tag}}</label>
                        <div class="col-md-9 col-sm-9 ">
                          {{formUser.th_wali}}
                        </div>
                      </div>