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

我经常在其他堆栈溢出问题上看到关于如何使用except: pass的build议。 为什么这不好? 有时我只是不在乎错误是什么,我只想继续执行代码。

 try: something except: pass 

为什么使用except: pass块坏? 是什么让它变坏? 这是事实,我pass一个错误,或者我except任何错误?

正如您正确猜测的那样,它有两个方面:通过在except指定任何exceptiontypes来捕获任何错误,并简单地传递它,而不采取任何行动。

我的解释是“稍长一点”,所以它分解为:

  1. 不要发现任何错误 。 始终指定您准备从哪些例外中恢复,并且只能抓住这些例外。
  2. 尽量避免通过除了块 。 除非明确要求,否则这通常不是一个好兆头。

但是让我们来详细说明一下:

不要发现任何错误

当使用try块时,通常是这样做的,因为你知道有抛出exception的机会。 因此,你也已经有了一个大致的想法,可以打破什么,可以抛出什么exception。 在这种情况下,您会发现一个例外,因为您可以积极地从中恢复过来 。 这意味着你已经为这个例外做好了准备,并且有一些替代的计划,在这种例外的情况下你会遵循。

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

但是,如果我们要抓住一切 ,那么除了那些例外,我们准备从中恢复过来 – 我们也有机会得到我们没有预料到的例外,而且我们确实无法从中恢复; 或者不应该从中恢复。

让我们从上面的configuration文件的例子。 在文件丢失的情况下,我们只是应用了我们的默认configuration,并可能在稍后决定自动保存configuration(所以下一次,文件存在)。 现在想象我们得到一个IsADirectoryError ,或一个PermissionError 。 在这种情况下,我们可能不想继续; 我们仍然可以应用我们的默认configuration,但我们以后将无法保存文件。 而且用户可能也想要自定义configuration,所以使用默认值可能是不希望的。 所以我们想立即告诉用户,也可能中止程序的执行。 但是这不是我们想在某个小代码部分深处做的事情; 这是应用程序级别的重要性,所以应该在顶部处理 – 所以让例外冒出来。

另一个简单的例子也是在Python 2习语文档中提到的。 在这里,代码中存在一个简单的错字,导致错误。 因为我们捕捉每个exception,所以我们也捕获NameErrorSyntaxError 。 两者都是编程时发生在我们身上的错误; 而且这两个都是我们绝对不想在运输代码时包含的错误。 但是因为我们也抓到了这些,所以我们甚至不知道它们是在那里发生的,失去了正确debugging的帮助。

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

无论如何,你已经准备好了一小部分代码中的所有东西,所以这就是你应该只捕获你准备好的exception的地方。 有些人build议至less要捕​​获Exception因为它不包括像SystemExitKeyboardInterrupt这样的devise将终止你的应用程序,但我认为这还是太不明确了。 只有一个地方我亲自接受捕获Exception或者只是任何exception,这是在一个单一的全局应用程序级exception处理程序,它有单一目的logging我们没有准备的任何exception。 这样,我们仍然可以保留关于意外exception的信息,然后我们可以使用它来扩展我们的代码以明确地处理这些exception(如果我们可以从中恢复的话),或者在发生错误的情况下创buildtesting用例来确保它不会再发生。 但是,当然,只有当我们发现那些我们已经期待的例外时,它才有用,所以我们没有想到的例子自然会冒出来。

尽量避免通过除了块

当明确地捕捉到一小部分特定的例外情况时,有很多情况下我们会毫无作为地处于好的状态。 在这种情况下, except SomeSpecificException: pass就好了。 大多数情况下,情况并非如此,因为我们可能需要一些与恢复过程相关的代码(如上所述)。 这可以是例如再次重试动作或者设置默认值的东西。

如果情况并非如此,例如,因为我们的代码已经被重构,直到成功,那么传递就足够了。 以我们上面的例子来说,我们可能要求用户input一个数字。 因为我们知道用户不喜欢我们要求他们的东西,所以我们可能只是把它放在一个循环中,所以可能看起来像这样:

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

因为我们不断尝试,直到没有exception抛出,所以我们不需要在except块中做任何特殊的事情,所以这很好。 但是,当然,有人可能会争辩说,我们至less想向用户显示一些错误信息,告诉他为什么他必须重复input。

然而,在许多其他情况下,仅仅通过一个except是一个迹象,表明我们并没有真正准备好迎接这个例外。 除非这些exception是简单的(比如ValueErrorTypeError ),我们可以通过的原因是显而易见的,尽量避免传递。 如果真的没有什么可做的(而且你完全肯定),那么考虑加一个评论为什么是这样的; 否则,扩展except块实际上包含一些恢复代码。

except: pass

最严重的罪犯是两者的结合。 这意味着我们很乐意接受任何错误,尽pipe我们绝对没有做好准备我们也没有做任何事情。 你至less要logging这个错误,也可能重新评估它,以便终止应用程序(在MemoryError之后,你不可能像正常那样继续)。 只要通过,不仅可以使应用程序保持一定的活力(取决于你当前的位置),还可以丢弃所有的信息,从而不可能发现错误 – 如果你不是发现错误的话,尤其如此。


所以底线是:只抓住你真正期望的并准备好恢复的例外; 所有其他人都可能是你应该修正的错误,或者你还没准备好的东西。 如果你真的不需要做某些事情,那么传递特定的exception就好了。 在所有其他情况下,这只是一个推定和懒惰的迹象。 你一定要解决这个问题。

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

这太多了。 在你的脑海里,你在想“我想忽略这个networking错误”。 如果意想不到的事情出了问题,那么你的代码就会默默地继续下去,并以完全不可预知的方式破坏,任何人都无法debugging。

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

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

 try: something except: pass 

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

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

为什么这不好?

 try: something except: pass 

这会捕获每个可能的exception,包括GeneratorExitKeyboardInterruptSystemExit ,这些都是您可能不想捕获的exception。 这跟捕获BaseException是一样的。

 try: something except BaseException: pass 

旧版本的文档说 :

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

Pythonexception层次结构

如果你捕获一个父exception类,你也抓住了他们所有的子类。 只有抓住你准备处理的例外情况,才更为优雅。

这里是Python 3的exception层次结构 – 你真的想抓住他们吗?

 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 

不要这样做

如果您使用这种forms的exception处理:

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

那么你将无法用Ctrl-C打断你的something 。 您的程序将忽略try代码块中的每个可能的exception。

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

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

相反,尽量只捕捉你知道你正在寻找的特定exception。 例如,如果您知道您可能会在转化时遇到价值错误:

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

如果其他人要求你完成,以便他们可以依靠你的代码,我明白感觉不得不处理一切。 但是,如果你愿意在开发过程中喧嚣失败,那么你将有机会纠正可能只是间歇性地出现的问题,但这将是长期的昂贵的错误。

通过更精确的error handling,您的代码可以更加健壮。

 >>> import this 

Python的禅宗,由蒂姆·彼得斯

美丽胜过丑陋。
显式比隐式更好。
简单胜于复杂。
复杂比复杂好。
平面比嵌套更好。
稀疏比密集好。
可读性计数。
特例不足以打破规则。
虽然实用性胜过纯净。
错误不应该默默通过。
除非明确沉默。
面对歧义,拒绝猜测的诱惑。
应该有一个 – 最好只有一个 – 明显的方法来做到这一点。
尽pipe这种方式一开始可能并不明显,除非你是荷兰人。
现在比从未好。
虽然从来没有比现在更好。
如果实施很难解释,这是一个坏主意。
如果实施很容易解释,这可能是一个好主意。
命名空间是一个好主意 – 让我们做更多的!

所以,这是我的意见。 每当你发现一个错误,你应该做一些处理它,即写在日志文件或其他东西。 至less,它告诉你,曾经有一个错误。

至less应该使用except Exception:以避免捕获系统exception(如SystemExitKeyboardInterrupt 。 这里是文档的链接 。

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

except:pass构造基本上消除了在try:块中覆盖的代码正在运行时出现的所有特殊情况。

是什么让这种不好的做法是,它通常不是你真正想要的。 更多的时候,一些特定的情况即将出现,你想沉默, except:pass是一个钝器太多。 它将完成工作,但它也将掩盖其他可能没有预料到的错误情况,但很可能需要以其他方式处理。

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

第一个原因已经被陈述 – 它隐藏了你没有想到的错误。

(#2) – 它让你的代码难以被他人阅读和理解。 如果您在尝试读取文件时发现FileNotFoundException,那么对于另一个开发人员而言,“catch”块应该具有哪些function是非常明显的。 如果你没有指定一个exception,那么你需要额外的注释来解释块应该做什么。

(#3) – 它展示了懒惰的编程。 如果使用genericstry / catch,则表明您不了解程序中可能发生的运行时错误,或者您不知道Python中可能有哪些exception。 捕获一个特定的错误表明你理解你的程序和Python引发的错误范围。 这更可能使其他开发人员和代码审查人员相信您的工作。

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

  • 显式比隐式更好
  • 错误不应该默默通过

这意味着,你故意让你的错误无声地通过。 而且,你不会事件知道,究竟发生了哪个错误,因为except: pass会捕获任何exception。

其次,如果我们试图从Python的禅宗中抽象出来,用纯正的语言来说话,那么你应该知道,使用except:pass会让你在系统中没有任何知识和控制 。 经验法则是在发生错误时引发exception,并采取适当的措施。 如果事先不知道,应该采取什么行动,至less在某个地方logging错误(最好是重新提升例外):

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

但是,通常情况下, 如果您尝试捕捉任何exception,您可能是做错了!

一般而言,您可以将错误/exception分为三类 :

  • 致命 :不是你的错,你不能阻止他们,你不能从他们身上恢复过来。 你当然不应该忽视它们并继续下去,并将你的程序置于未知状态。 只要让错误终止你的程序,就没有什么可以做的。

  • Boneheaded :你自己的错,很可能是由于监督,错误或编程错误。 你应该修复这个错误。 再次,你绝对不应该忽视和继续。

  • 外生的 :在exception的情况下,你可以预料到这些错误,例如文件未find连接终止 。 你应该明确地处理这些错误,只有这些。

在所有情况下, except: pass只会让你的程序处于一个未知的状态,在那里会造成更多的伤害。

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

 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" 

现在想象一下tryexcept块是对复杂对象层次结构的数百行调用,本身在大型程序的调用树中被调用。 当程序出错时,你从哪里开始看?

简而言之,如果抛出exception或错误,就会出现错误。 这可能不是非常错误的,但是为了使用goto语句而创build,抛出和捕获错误和exception并不是一个好主意,而且很less这样做。 99%的时间,有一个地方的问题。

问题需要处理。 就像人生如何,在编程方面,如果只留下问题,不去理会,就不会自己走很多次; 相反,他们变得更大,更多。 为了防止问题在你身上蔓延,并在路上再次发生,你可以:1)把它清理干净,然后把它收拾起来,然后把垃圾清理干净。

只是忽略exception和错误,让它们像这样是一个很好的方法来体验内存泄漏,优秀的数据库连接,不必要的locking文件权限等。

在极less数情况下,这个问题是微乎其微的,除了需要一个尝试……捕捉块 – 自成一体 ,事后才清理干净。 这些是唯一的情况下,这种最佳做法不一定适用。 根据我的经验,这通常意味着,无论代码是做什么的,基本上都是小的,可以放弃的,像重试或特殊的信息既不值得复杂也不值得。

在我的公司里,规则几乎总是在一个大块头上做些事情,如果你什么都不做,那么你总是要以一个非常好的理由发表评论。 当有任何事情要做时,你绝对不能通过或留下一个空的catch块。

在我看来,错误有理由出现,我的声音很愚蠢,但事实就是如此。 好的编程只会在你必须处理它们时引发错误。 另外,正如我前段时间看到的,“pass-statement是一个陈述代码稍后会被插入的陈述”,所以如果你想有一个空的except语句,可以随意这样做,但是对于一个好的程序来说,成为失踪的一部分。 因为你不处理你应该拥有的东西。 出现的exception使您有机会更正input数据或更改数据结构,以免再次出现这些exception(但在大多数情况下(networkingexception,常规inputexception)exception表示程序的下一部分无法正常运行。例如,一个NetworkException可以指示一个断开的networking连接,程序不能在下一个程序步骤中发送/接收数据。

但是对于只有一个执行块使用pass块是有效的,因为你仍然在exceptiontypes之间进行区分,所以如果你把所有的exception块放在一个块中,它就不是空的:

 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块,也可能导致代码的结构处理特殊types的exception。

所有提出的评论都是有效的。 在可能的情况下,您需要指定您要忽略的确切例外。 在可能的情况下,您需要分析是什么引起了exception,而只是忽略了您的意思,而不是其他的。 如果exception导致应用程序“惊人地崩溃”,那就这样做吧,因为知道事件发生的意外情况要比隐瞒问题的发生重要得多。

尽pipe如此,不要把任何编程习惯作为首要任务。 这很愚蠢。 总是有时间和地点做忽略所有例外块。

另一个白痴最重要的例子是使用goto算子。 当我在学校时,我们的教授教导我们,只要提到不要使用它,永远。 不要相信别人告诉你,xyz永远不会被使用,当它有用的时候不会有任何情况。 总有。

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