除了:pass"糟糕的编程实践?

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

try:
something
except:
pass

为什么使用except: pass块不好?是什么让它变得糟糕?这是事实,我pass错误或我except任何错误?

282529 次浏览

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

try:
something
except:
pass

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

你应该至少使用except Exception:来避免捕获像SystemExitKeyboardInterrupt这样的系统异常。这里是链接到docs。

一般来说,您应该显式地定义想要捕获的异常,以避免捕获不想要的异常。你应该知道你忽略是什么异常。

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

  • 显性比隐性好
  • 错误绝不能悄无声息地过去

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

其次,如果我们试图从Python的禅宗中抽象出来,并从理智的角度来说话,你应该知道,使用except:pass会在你的系统中留下没有知识和控制。经验法则是,如果发生错误,就引发异常,并采取适当的操作。如果你事先不知道这些操作应该是什么,至少在某个地方记录错误(最好重新引发异常):

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

但是,通常是如果您试图捕获任何异常,那么您可能正在做错误的事情!

>>> import this

蒂姆·彼得斯的《Python之禅》

美丽总比丑陋好 显式优于隐式
简单总比复杂好
复杂胜过复杂。
Flat优于nested.
稀疏优于密集
可读性。

.
. 尽管实用性胜过纯洁性 错误绝不能悄无声息地过去。
除非显式沉默。
在模棱两可的情况下,拒绝猜测的诱惑 应该有一种——最好只有一种——明显的方法来做到这一点 尽管这种方式一开始可能不明显,除非你是荷兰人
.现在总比没有好 虽然never通常比正确的 now更好 如果实现很难解释,这是一个坏主意 如果实现很容易解释,这可能是一个好主意 名称空间是一个非常棒的想法——让我们多做一些吧!< / p >

所以,这是我的观点。每当你发现一个错误,你应该做一些事情来处理它,即把它写在日志文件或其他东西。至少,它告诉您曾经有一个错误。

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

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

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

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

我的解释“有点”长,所以可以分解成这样:

  1. 不捕捉任何错误。始终指定准备从哪些异常中恢复,并只捕获这些异常。
  2. 尽量避免传入除块。除非有明确的愿望,否则这通常不是一个好迹象。

但让我们来详细谈谈:

不要捕捉任何错误

当使用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之后,你不太可能像正常情况下那样继续)。仅仅通过它不仅会使应用程序保持一定程度的活跃(当然,这取决于您捕获的位置),而且还会丢弃所有信息,使得不可能发现错误——如果您不是发现错误的人,则尤其如此。


因此,底线是:只捕获您真正期望并准备从中恢复的异常;所有其他的可能要么是你应该改正的错误,要么是你根本没有准备好的事情。如果你真的不需要对它们做什么,传递具体的异常是可以的。在所有其他情况下,这只是一种傲慢和懒惰的表现。你肯定想解决这个问题。

except:pass构造本质上是在运行try:块中包含的代码时,使出现的任何和所有异常情况保持沉默。

这种糟糕的做法是因为它通常不是你真正想要的。通常情况下,一些特定的情况出现,你想要沉默,而except:pass是一个太钝的工具。它将完成工作,但它也会掩盖其他错误条件,您可能没有预料到,但可能非常希望以其他方式处理。

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

到目前为止提出的所有意见都是有效的。在可能的情况下,您需要指定想要忽略的异常。在可能的情况下,你需要分析导致异常的原因,只忽略你想要忽略的部分,而不是其他部分。如果异常导致应用程序“壮观地崩溃”,那么就这样吧,因为知道意外发生的时间比隐藏问题发生的时间要重要得多。

综上所述,不要将任何编程实践视为至高无上的。这太愚蠢了。总有时间和地点可以执行“忽略所有异常”块。

另一个白痴派拉蒙的例子是goto操作符的使用。当我在学校的时候,我们的教授教我们goto运算符只是为了提醒你永远不能使用它。不要相信人们告诉你xyz永远不应该被使用,也不可能有一个场景它是有用的。总是有的。

为什么“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

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

如果其他人要求你完成,这样他们就可以依赖你的代码,我理解那种被迫处理所有事情的感觉。但如果你愿意在开发过程中遭遇失败,你就有机会纠正那些偶尔出现的问题,但这将是代价高昂的长期错误。

有了更精确的错误处理,代码就会更加健壮。

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

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

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

问题需要处理。就像在生活中一样,在编程中,如果你把问题放在一边,试着忽略它们,很多时候它们不会自己消失;相反,它们变得越来越大,越来越多。为了防止一个问题在你身上滋生,并在将来再次出现,你要么1)消除它,然后清理混乱,要么2)控制它,然后清理混乱。

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

在极少数情况下,这个问题是如此微不足道,微不足道,而且——除了需要尝试……catch block - 自包含的,之后真的没有乱七八糟的东西需要清理。只有在这些情况下,这种最佳实践并不一定适用。根据我的经验,这通常意味着无论代码在做什么,基本上都是微不足道的,可以忽略的,而像重试尝试或特殊消息这样的事情既不值得复杂,也不值得暂停线程。

在我的公司,规则是几乎总是在catch块中执行某物,如果你什么都不做,那么你必须总是放置一个注释,并给出一个非常好的理由。当有事情要做的时候,你绝对不能错过或留下一个空的catch块。

第一个原因已经说过了——它隐藏了你没有预料到的错误。

如果你在试图读取文件时捕获FileNotFoundException异常,那么对另一个开发人员来说,'catch'块应该具有什么功能是非常明显的。如果您没有指定异常,那么您需要额外的注释来解释该块应该做什么。

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

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

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

通常,你可以在三个类别中对任何错误/异常进行分类:

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

  • /strong>:你自己的错,很可能是由于疏忽、bug或编程错误。你应该修复这个bug。同样,你不应该忽视并继续。

  • 外生的:在例外情况下,您可以预期这些错误,例如文件未找到连接终止。您应该显式地处理这些错误,而且只能处理这些错误。

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

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

因为它还没有被提及,所以使用contextlib.suppress是更好的样式:

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

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

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

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

但是这个加薪不会有任何效果因为它是和任何可能剩下的堆栈的底部。

顺便说一句,这不是万能的灵丹妙药。有一些例外情况是无法被发现的。SNMP不能保证传输。YMMV。

如果这是不好的做法"pass"这是不可能的。 如果你有一个资产可以从很多地方接收信息,比如一个表单或者userInput,那么它就会派上用场了

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

我个人更喜欢这个解决方案:

except ValueError as error:
print(error.args)
pass

error.args给了我一个不是太分散注意力,但真正有助于代码审查,特别是如果有不同的错误原因,如

(ValueError('year 0 is out of range'),)
(ValueError('month must be in 1..12'),)
(ValueError('day is out of range for month'),)

当使用pandas中的时间段时。