Why is “except: pass” a bad programming practice?
我经常看到对其他堆栈溢出问题的评论,这些问题是关于如何不鼓励使用
1 2 3 4 | try: something except: pass |
为什么使用
正如您正确猜测的那样,它有两个方面:通过在
我的解释"有点长",所以tl;dr它可以分解为:好的。
但是,让我们详细分析一下:好的。不捕捉任何错误
当使用
例如,当您要求用户输入一个数字时,可以使用
然而,如果我们要抓住每一件事,那么除了那些我们准备从中恢复的异常之外,还有一个机会就是我们得到了我们没有预料到的异常,并且我们确实无法从中恢复;或者不应该从中恢复。好的。
让我们以上面的配置文件为例。如果文件丢失,我们只应用默认配置,稍后可能会决定自动保存配置(因此下次文件存在)。现在假设我们得到一个
另一个简单的例子也在python 2习语文档中提到。这里,代码中存在一个简单的打字错误,导致代码中断。因为我们捕捉到了每一个异常,所以我们也捕捉到了
但也有更危险的例外,我们不太可能做好准备。例如,系统错误通常是很少发生的事情,我们不能真正计划;这意味着正在发生更复杂的事情,可能会阻止我们继续当前的任务。好的。
在任何情况下,您都不太可能在代码的小范围内为所有事情做好准备,所以这实际上是您应该只捕获那些准备好的异常的地方。有些人建议至少抓到
当显式地捕获一小部分特定异常时,在许多情况下,我们只需什么都不做就可以了。在这种情况下,仅仅拥有
但是,如果不是这样的话,例如,因为我们的代码已经被构造为重复直到成功,那么仅仅传递就足够了。以上面的示例为例,我们可能希望用户输入一个数字。因为我们知道用户不喜欢做我们要求他们做的事情,所以我们可能首先把它放进一个循环中,所以它看起来像这样:好的。
1 2 3 4 5 6 | def askForNumber (): while True: try: return int(input('Please enter a number: ')) except ValueError: pass |
因为我们一直在尝试直到没有异常被抛出,所以我们不需要在except块中做任何特殊的事情,所以这很好。当然,有人可能会说,我们至少想向用户显示一些错误消息,告诉他为什么必须重复输入。好的。
不过,在许多其他情况下,仅仅传入一个
但最严重的罪犯是两者的结合。这意味着我们愿意抓住任何错误,尽管我们完全没有准备,我们也没有做任何事情。您至少要记录错误,也可能重新发出它以终止应用程序(在发生内存错误后,您不太可能像正常一样继续)。只是传递消息不仅可以使应用程序保持一定的活动状态(当然,这取决于您在哪里捕获的信息),而且还可以丢弃所有信息,从而使您不可能发现错误,如果您不是发现错误的人,那么这一点尤其正确。好的。
因此,底线是:只捕获您真正期望并准备从中恢复的异常;其他所有异常都可能是您应该修复的错误,或者是您无论如何都没有准备好的错误。如果您真的不需要对特定的异常做些什么,那么可以通过它们。在所有其他情况下,这只是一种自以为是和懒惰的表现。你肯定想解决这个问题。好的。好啊。
这里的主要问题是它忽略了所有和任何错误:内存不足、CPU烧坏、用户想要停止、程序想要退出、Jabberwocky正在杀死用户。
这太过分了。在你的头脑中,你在想"我想忽略这个网络错误"。如果发生意外错误,那么您的代码将以完全不可预知的方式静默地继续并中断,没有人可以调试它。
这就是为什么你应该限制自己只忽略一些错误,让其余的错误通过。
从字面上执行伪代码甚至不会产生任何错误:
1 2 3 4 | try: something except: pass |
就好像它是一段完全有效的代码,而不是抛出一个
Why is"except: pass" a bad programming practice?
Why is this bad?
1
2
3
4 try:
something
except:
pass
这捕获了所有可能的异常,包括
1 2 3 4 | try: something except BaseException: pass |
旧版本的文档说:
Since every error in Python raises an exception, using
except: can make many programming errors look like runtime problems, which hinders the debugging process.
python异常层次结构
如果捕获一个父异常类,那么也将捕获其所有子类。只捕获准备处理的异常会更优雅。
下面是python 3异常层次结构-您真的想全部捕获它们吗?:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | 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 |
不要这样做
如果使用这种异常处理形式:
1 2 3 4 | try: something except: # don't just do a bare except! pass |
然后,您将无法用ctrl-c中断您的
下面是另一个具有相同不良行为的例子:
1 2 | except BaseException as e: # don't do this either - same as bare! logging.info(e) |
相反,尝试只捕获您知道要查找的特定异常。例如,如果知道转换时可能会出现值错误:
1 2 3 4 5 6 7 8 | 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. |
用另一个例子进一步解释
你这样做可能是因为你一直在网上抓取,比如说,一个
如果其他人要求您完成,以便他们可以依赖于您的代码,我理解您必须处理所有事情。但是,如果您愿意在开发过程中大声失败,那么您将有机会纠正可能只是间歇性出现的问题,但这将是长期代价高昂的错误。
通过更精确的错误处理,您的代码可以更健壮。
1 | >>> import this |
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
所以,我的意见是。每当你发现一个错误时,你应该做一些事情来处理它,即把它写在日志文件或其他什么文件中。至少,它会通知您曾经有一个错误。
您应该至少使用
一般来说,您应该明确定义要捕获的异常,以避免捕获不需要的异常。你应该知道你忽略了哪些例外。
首先,它违反了Python禅的两个原则:
- 显式优于隐式
- 错误永远不会悄悄地过去。
它的意思是,你故意让你的错误安静地过去。此外,您不知道具体发生了哪个错误,因为
第二,如果我们试着从Python的禅宗中抽象出来,用公正的理智说话,你应该知道,使用
1 2 3 4 | try: something except: logger.exception('Something happened') |
但是,通常,如果你试图抓住任何例外,你很可能是做错了什么!
已经说明了1原因-它隐藏了您没有预料到的错误。
(2)-这会使您的代码难以让其他人阅读和理解。如果您在尝试读取文件时捕获了fileNotFoundException,那么对于另一个开发人员来说,"catch"块应该具有什么功能是非常明显的。如果不指定异常,则需要附加注释来解释块应该做什么。
(3)-它演示了懒惰的编程。如果使用通用的Try/Catch,则表明您不了解程序中可能出现的运行时错误,或者不知道在Python中可能出现哪些异常。捕捉一个特定的错误表明您了解了您的程序和Python抛出的错误范围。这更有可能使其他开发人员和代码评审人员相信您的工作。
造成这种坏习惯的原因是它通常不是你真正想要的。更常见的是,一些特定的情况出现了,你想沉默,而
在Python中,这一点尤为重要的是,根据这种语言的习惯用法,异常不一定是错误的。当然,它们通常是这样使用的,就像大多数语言一样。但是,特别是python偶尔会使用它们来实现一些代码任务的可选退出路径,这些代码任务实际上不是正常运行情况的一部分,但仍然知道会不时出现,在大多数情况下甚至可能出现。
那么,这个代码产生什么输出?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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" |
现在假设
通常,您可以将任何错误/异常分为三类:
致命的:不是你的错,你不能阻止他们,你不能从他们身上恢复。您当然不应该忽略它们并继续,让您的程序处于未知状态。只要让错误终止您的程序,您就无能为力了。
愚蠢的:你自己的错误,很可能是由于疏忽、错误或编程错误。你应该修正这个错误。同样,你也不应该忽视和继续。
外生的:您可以在异常情况下期望这些错误,例如找不到文件或连接终止。您应该显式地处理这些错误,并且只处理这些错误。
在所有情况下,
在我看来,错误是有原因出现的,我的声音听起来很愚蠢,但事实就是这样。好的编程只会在必须处理错误时引发错误。另外,正如我前一段时间读到的,"pass语句是一个显示代码将在稍后插入的语句",因此如果您想要一个空的except语句,请随意这样做,但是对于一个好的程序,会有一个部分丢失。因为你不能处理你应该拥有的东西。出现异常会使您有机会纠正输入数据或更改数据结构,使这些异常不会再次发生(但在大多数情况下(网络异常、一般输入异常)异常表明程序的下一个部分执行不好。例如,networkexception可以指示断开的网络连接,并且程序无法在下一个程序步骤中发送/接收数据。
但是只对一个执行块使用pass块是有效的,因为在异常类型之间仍然存在差异,因此如果将所有异常块放在一个中,则它不是空的:
1 2 3 4 5 6 7 8 | try: #code here except Error1: #exception handle1 except Error2: #exception handle2 #and so on |
可以这样重写:
1 2 3 4 5 6 7 8 9 10 11 12 13 | try: #code here except BaseException as e: if isinstance(e, Error1): #exception handle1 elif isinstance(e, Error2): #exception handle2 ... else: raise |
因此,即使多个带有pass语句的except块也可能导致代码,其结构处理特殊类型的异常。
简单地说,如果抛出异常或错误,就会出现问题。这可能不是什么大错误,但是仅仅为了使用goto语句而创建、抛出和捕获错误和异常并不是一个好主意,而且很少这样做。99%的时候,某个地方出现了问题。
问题需要处理。就像生活中,编程中一样,如果你把问题放在一边,试着忽略它们,它们不仅会自己消失很多次,反而会变得越来越大,越来越多。为了防止一个问题在你身上滋生,并再次沿着道路继续前进,你要么1)消除它,然后清理这些乱七八糟的东西,要么2)遏制它,然后清理这些乱七八糟的东西。
只需忽略异常和错误,并像这样保留它们,就可以很好地体验内存泄漏、出色的数据库连接、不必要的文件权限锁等等。
在极少数情况下,问题是如此的微小,琐碎,而且-除了需要一个尝试…捕获块-自给自足,以至于真正没有混乱需要清理后。只有在这些情况下,这种最佳实践并不一定适用。在我的经验中,这通常意味着无论代码在做什么,基本上都是琐碎的和可原谅的,并且像重试尝试或特殊消息之类的东西既不值得复杂也不值得保持线程。
在我的公司,规则是几乎总是在一个捕获块中做一些事情,如果你不做任何事情,那么你必须总是用一个非常好的理由来发表评论。当有任何事情要做时,您决不能通过或离开空的catch块。
迄今为止提出的所有意见都是有效的。在可能的情况下,您需要指定要忽略的异常。在可能的情况下,您需要分析导致异常的原因,并且只忽略您要忽略的内容,而不忽略其余的内容。如果异常导致应用程序"惊人地崩溃",那么就这样吧,因为知道发生意外时发生的情况比隐瞒问题曾经发生更重要。
尽管如此,不要把任何编程实践作为首要任务。这太愚蠢了。总是有时间和地点可以忽略所有异常块。
另一个白痴派拉蒙的例子是使用
?处理错误在编程中非常重要。您确实需要向用户显示出了什么问题。在极少数情况下,您可以忽略错误。这是非常糟糕的编程实践。
由于还没有提到,所以最好使用
1 2 | with suppress(FileNotFoundError): os.remove('somefile.tmp') |
请注意,在提供的示例中,无论异常是否发生,程序状态都保持不变。也就是说,