提问人:Vader 提问时间:2/4/2014 最后编辑:ClebVader 更新时间:3/10/2022 访问量:327322
为什么“except: pass”是一种糟糕的编程实践?
Why is "except: pass" a bad programming practice?
问:
我经常看到关于其他 Stack Overflow 问题的评论,关于如何不鼓励使用 of。为什么这很糟糕?有时我只是不在乎错误是什么,我只想继续编写代码。except: pass
try:
something
except:
pass
为什么使用块不好?是什么让它变得糟糕?是我犯了错误还是我犯了任何错误?except: pass
pass
except
答:
从字面上执行伪代码甚至不会给出任何错误:
try:
something
except:
pass
就好像它是一段完全有效的代码,而不是抛出一个.我希望这不是你想要的。NameError
您至少应该使用 来避免捕获系统异常,例如 或 。这是文档的链接。except Exception:
SystemExit
KeyboardInterrupt
通常,应显式定义要捕获的异常,以避免捕获不需要的异常。您应该知道您忽略了哪些异常。
首先,它违反了 Python 禅宗的两个原则:
- 显式比隐式好
- 错误永远不应该默默地传递
这意味着,你故意让你的错误默默地通过。此外,您不知道到底发生了哪个错误,因为会捕获任何异常。except: pass
其次,如果我们试图从Python的禅宗中抽象出来,并用理智来说话,你应该知道,使用会让你对你的系统没有任何知识和控制。经验法则是在发生错误时引发异常,并采取适当的措施。如果您事先不知道这些应该采取什么行动,至少将错误记录在某处(最好重新引发异常):except:pass
try:
something
except:
logger.exception('Something happened')
但是,通常情况下,如果你试图发现任何异常,你可能做错了什么!
评论
>>> import this
Python 的禅宗,蒂姆·彼得斯 (Tim Peters) 着
美丽总比丑陋好。
显式比隐式更好。
简单胜于复杂。
复杂总比复杂好。
扁平比嵌套好。
稀疏比密集好。
可读性很重要。
特殊情况还不足以打破规则。
虽然实用性胜过纯粹性。
错误永远不应该以静默方式传递。
除非明确静音。
面对暧昧,拒绝猜测的诱惑。
应该有一种——最好只有一种——显而易见的方法来做到这一点。
尽管这种方式一开始可能并不明显,除非你是荷兰人。
现在总比没有好。
虽然从来没有比现在更好。
如果实现难以解释,那就是一个坏主意。
如果实现易于解释,这可能是一个好主意。
命名空间是一个令人振奋的好主意——让我们做更多这样的事吧!
所以,这是我的意见。每当你发现一个错误时,你都应该做一些事情来处理它,即把它写在日志文件或其他东西中。至少,它会通知您曾经存在错误。
评论
这里的主要问题是它忽略了所有错误:内存不足,CPU正在燃烧,用户想要停止,程序想要退出,Jabberwocky正在杀死用户。
这太过分了。在你的脑海中,你在想“我想忽略这个网络错误”。如果出现意外情况,那么您的代码会以完全不可预测的方式继续并以完全不可预测的方式中断,没有人可以调试。
这就是为什么你应该限制自己只忽略一些错误,让其余的错误过去。
正如您正确猜到的那样,它有两个方面:通过在 之后不指定异常类型来捕获任何错误,并且只需传递它而不采取任何操作。except
我的解释是“有点”长——所以 tl;DR IT 分解为:
- 不要发现任何错误。始终指定准备从中恢复的异常,并仅捕获这些异常。
- 尽量避免传入,但块除外。除非明确要求,否则这通常不是一个好兆头。
但是,让我们详细介绍一下:
不捕获任何错误
使用块时,您通常会这样做,因为您知道有可能引发异常。因此,您也已经大致了解了哪些可以中断,哪些异常可以抛出。在这种情况下,您会发现异常,因为您可以积极地从中恢复。这意味着您已经为异常做好了准备,并且有一些替代计划,如果出现该异常,您将遵循这些计划。try
例如,当您要求用户输入一个数字时,您可以转换可能引发 ValueError
的输入。您可以通过简单地要求用户重试来轻松恢复它,因此捕获并再次提示用户将是一个合适的计划。另一个例子是,如果您想从文件中读取某些配置,而该文件恰好不存在。由于它是一个配置文件,因此您可能有一些默认配置作为回退,因此该文件并不完全需要。因此,捕获 FileNotFoundError
并简单地应用默认配置将是一个很好的计划。现在,在这两种情况下,我们都有一个非常具体的例外,我们期望并有一个同样具体的计划来从中恢复。因此,在每种情况下,我们明确地只允许某些例外。int()
ValueError
except
但是,如果我们要捕获所有内容,那么,除了我们准备从中恢复的异常之外,我们还有可能遇到我们没有预料到的异常,并且我们确实无法从中恢复;或者不应该从中恢复。
让我们以上面的配置文件为例。如果缺少文件,我们只是应用了默认配置,并可能在以后决定自动保存配置(因此下次文件存在)。现在,假设我们得到一个 IsADirectoryError
,或者一个 PermissionError
。在这种情况下,我们可能不想继续;我们仍然可以应用默认配置,但以后将无法保存文件。而且用户很可能也打算使用自定义配置,因此可能不希望使用默认值。因此,我们希望立即告诉用户,并且可能也会中止程序执行。但这不是我们想在某个小代码部分的深处做的事情;这是应用程序级别的重要内容,因此应该在顶部处理,因此让异常冒泡。
Python 2 习语文档中也提到了另一个简单的例子。在这里,代码中存在一个简单的拼写错误,导致它中断。因为我们捕获了每个异常,所以我们也捕获了 NameError
s 和 SyntaxError
s。这两个错误都是我们在编程时都会发生的错误,而且都是我们在发布代码时绝对不想包含的错误。但是因为我们也抓住了这些,我们甚至不知道它们发生在那里,并且失去了任何帮助来正确调试它。
但也有更危险的例外情况,我们不太可能做好准备。例如,SystemError 通常是很少发生的事情,我们无法真正计划;这意味着正在发生一些更复杂的事情,这可能会阻止我们继续当前的任务。
无论如何,你不太可能为代码的一小部分中的所有内容做好准备,所以这确实是你应该只捕捉那些你准备好的异常的地方。有些人建议至少抓住 Exception
,因为它不会包含诸如 和 哪些设计是为了终止您的应用程序之类的内容,但我认为这仍然太不具体了。我个人只接受一个地方的捕获或任何异常,那就是在单个全局应用程序级异常处理程序中,它的唯一目的是记录我们没有准备好的任何异常。这样,我们仍然可以保留尽可能多的有关意外异常的信息,然后我们可以使用这些信息来扩展我们的代码以显式处理这些异常(如果我们可以从中恢复),或者在出现错误的情况下创建测试用例以确保它不会再次发生。但是,当然,只有当我们只发现那些我们已经预料到的异常时,这才有效,所以那些我们没有预料到的异常会自然而然地冒出来。SystemExit
KeyboardInterrupt
Exception
尽量避免传入,但块除外
当显式捕获一小部分特定异常时,在许多情况下,我们只需什么都不做就可以了。在这种情况下,只要有就好了。但大多数时候,情况并非如此,因为我们可能需要一些与恢复过程相关的代码(如上所述)。例如,这可以是重试操作的内容,也可以是设置默认值。except SomeSpecificException: pass
如果情况并非如此,例如,因为我们的代码已经被结构化为重复,直到它成功,那么仅仅传递就足够了。以上面的例子为例,我们可能想要求用户输入一个数字。因为我们知道用户不喜欢做我们要求他们做的事情,所以我们可能一开始就把它放到一个循环中,所以它可能看起来像这样:
def askForNumber ():
while True:
try:
return int(input('Please enter a number: '))
except ValueError:
pass
因为我们一直在尝试,直到没有抛出异常,所以我们不需要在 except 块中做任何特殊的事情,所以这很好。但是,当然,有人可能会争辩说,我们至少要向用户显示一些错误消息,告诉他为什么他必须重复输入。
然而,在许多其他情况下,仅仅通过一个表明我们并没有真正为我们所发现的异常做好准备。除非这些异常很简单(如 or ),并且我们可以传递的原因很明显,否则请尽量避免只是传递。如果真的无事可做(而且你对此绝对确定),那么可以考虑添加一个评论,说明为什么会这样;否则,请展开 except 块以实际包含一些恢复代码。except
ValueError
TypeError
except: pass
然而,最糟糕的罪犯是两者的结合。这意味着我们心甘情愿地发现任何错误,尽管我们绝对没有为此做好准备,我们也没有采取任何措施。您至少希望记录错误,并可能重新引发它以仍然终止应用程序(在 MemoryError 之后,您不太可能像往常一样继续)。只是通过不仅会让应用程序保持某种程度的活跃(当然取决于你在哪里捕获),而且还会丢弃所有信息,使其无法发现错误——如果你不是发现它的人,则尤其如此。
因此,底线是:只捕获您真正期望并准备从中恢复的异常;所有其他错误可能要么是你应该纠正的错误,要么是你无论如何都没有准备好的东西。如果您真的不需要对它们执行某些操作,那么传递特定的异常是可以的。在所有其他情况下,这只是自以为是和懒惰的标志。你肯定想解决这个问题。
评论
except
raise
except
raise
raise Exception() from e
该构造实质上使运行块中涵盖的代码时出现的任何和所有异常情况保持沉默。except:pass
try:
造成这种不良做法的原因在于,它通常不是你真正想要的。更常见的是,一些特定的情况正在出现,你想保持沉默,而且太钝了。它将完成工作,但它也会掩盖您可能没有预料到的其他错误情况,但很可能希望以其他方式处理。except:pass
这在 Python 中特别重要的是,根据这种语言的习语,异常不一定是错误。当然,它们经常以这种方式使用,就像在大多数语言中一样。但 Python 偶尔会使用它们来实现某些代码任务的替代退出路径,这并不是正常运行情况的一部分,但仍然不时出现,甚至可能在大多数情况下是预期的。 已经提到过一个古老的例子,但现在最常见的例子可能是。以这种方式使用异常引起了很多争议,尤其是当迭代器和生成器首次引入 Python 时,但最终这个想法占了上风。SystemExit
StopIteration
到目前为止提出的所有评论都是有效的。在可能的情况下,您需要指定要忽略的确切异常。在可能的情况下,您需要分析导致异常的原因,并且只忽略您想要忽略的内容,而不是其余的。如果异常导致应用程序“惊人地崩溃”,那就这样吧,因为知道意外发生的时间比隐瞒问题曾经发生过要重要得多。
综上所述,不要将任何编程实践视为最重要的。这是愚蠢的。总有时间和地点可以执行忽略所有异常块。
另一个愚蠢的至高无上的例子是运算符的使用。当我在学校的时候,我们的教授教我们操作员,只是提到你永远不要使用它。不要相信人们告诉你永远不应该使用 xyz,也不可能有它有用的场景。总是有的。goto
goto
评论
为什么“except: pass”是一种糟糕的编程实践?
为什么这很糟糕?
try: something except: pass
这将捕获所有可能的异常,包括 、 和 - 您可能不打算捕获的异常。这和抓.GeneratorExit
KeyboardInterrupt
SystemExit
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 是一个 Shows 代码稍后将插入的语句”,所以如果你想有一个空的 except-statement 可以随意这样做,但对于一个好的程序来说,会缺少一部分。因为你没有处理你应该拥有的东西。出现的异常使您有机会更正输入数据或更改数据结构,以便这些异常不会再次发生(但在大多数情况下(网络异常、常规输入异常)异常表示程序的下一部分无法正常执行。例如,NetworkException 可以指示网络连接断开,并且程序无法在后续程序步骤中发送/接收数据。
但是只对一个 execption-block 使用 pass 块是有效的,因为你仍然区分异常的类型,所以如果你把所有的 exception-blocks 放在一个中,它不是空的:
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-statements 的多个 except-block 也会导致代码,其结构可以处理特殊类型的异常。
简单地说,如果抛出异常或错误,则说明有问题。这可能不是什么大错特错,但仅仅为了使用 goto 语句而创建、抛出和捕获错误和异常并不是一个好主意,而且很少这样做。99%的时间,某个地方有问题。
问题需要处理。就像在生活中一样,在编程中,如果你只是不理会问题并试图忽略它们,它们不会在很多时候自行消失;相反,它们变得更大并成倍增加。为了防止问题在你身上蔓延并进一步发生,你要么 1) 消除它并在事后清理烂摊子,要么 2) 控制它并在事后清理烂摊子。
只是忽略异常和错误并让它们保持原样是体验内存泄漏、未完成的数据库连接、不必要的文件权限锁定等的好方法。
在极少数情况下,问题是如此微不足道、微不足道,而且 - 除了需要尝试......Catch 块 - 自给自足,事后真的没有乱七八糟的东西需要清理。这些是此最佳实践不一定适用的唯一情况。根据我的经验,这通常意味着无论代码做什么,基本上都是微不足道的和可以原谅的,而像重试尝试或特殊消息这样的东西既不值得复杂性,也不值得阻碍线程。
在我的公司,规则是几乎总是在捕获块中做某事,如果你什么都不做,那么你必须始终发表评论,并有充分的理由不这样做。当有任何事情要做时,你绝不能通过或留下一个空的捕捉块。
#1 原因已经说明 - 它隐藏了您意想不到的错误。
(#2) - 它使其他人难以阅读和理解您的代码。如果在尝试读取文件时捕获 FileNotFoundException,那么对于其他开发人员来说,“catch”块应该具有哪些功能是显而易见的。如果未指定异常,则需要其他注释来解释块应执行的操作。
(#3) - 它演示了惰性编程。如果您使用通用的 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
评论
通常,您可以将任何错误/异常分类为以下三个类别之一:
致命:不是你的错,你无法阻止它们,你无法从它们中恢复过来。你当然不应该忽略它们并继续,让你的程序处于未知状态。只是让错误终止你的程序,你无能为力。
骨头:你自己的错,很可能是由于疏忽、错误或编程错误。您应该修复该错误。同样,您绝对不应该忽视并继续。
外部:在特殊情况下,例如找不到文件或连接终止,您可能会遇到这些错误。您应该显式处理这些错误,并且只处理这些错误。
在所有情况下,只会使您的程序处于未知状态,从而造成更大的损害。except: pass
处理错误在编程中非常重要。您确实需要向用户展示出了什么问题。在极少数情况下,您可以忽略这些错误。这是非常糟糕的编程实践。
由于它还没有被提及,所以使用 contextlib.suppress
是更好的样式:
with suppress(FileNotFoundError):
os.remove('somefile.tmp')
在此示例中,在执行此代码块后将不存在,而不会引发任何异常(除 ,该异常被抑制)。somefile.tmp
FileNotFoundError
我正在构建一个将在数据中心运行的应用程序。它不应产生任何错误或引发任何异常。我的数据中心有一个网络监控系统,其中包括一个SNMP陷阱接收器。
try:
main()
except as e:
log(str(e))
send_snmp_trap(str(e))
raise
除了 raise 不会去任何地方,因为它是可能剩下的任何堆栈的底部。
顺便说一句,这绝不是万能的灵丹妙药。有一些例外是无法捕获的。SNMP 不保证交付。YMMV。
如果这是不好的做法,“通过”将不是一种选择。 如果您有一个从许多地方接收信息的资产,即表单或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
下一个:我可以尝试/捕获警告吗?
评论
logging