我经常看到有关如何except: pass
阻止使用的其他Stack Overflow问题的评论.为什么这么糟糕?有时我只是不在乎错误是什么,我想继续使用代码.
try: something except: pass
为什么使用except: pass
块坏?是什么让它变坏?是因为我pass
出错了还是我except
有错误?
正如您所猜测的那样,它有两个方面:通过在之后指定无异常类型来捕获任何错误except
,并且只是在不采取任何操作的情况下传递它.
我的解释是"有点"更长 - 所以tl; dr它分解为:
不要抓到任何错误.始终指定您准备从中恢复的异常,并仅捕获这些异常.
尽量避免传入除块之外的传递.除非明确要求,否则这通常不是一个好兆头.
但让我们详细说明:
使用try
块时,通常这样做是因为您知道有可能抛出异常.因此,您也已经大致了解了什么可以破坏以及可以抛出什么异常.在这种情况下,您会捕获异常,因为您可以从中积极地恢复.这意味着您已为此例外做好准备,并制定了一些替代计划,以便在该例外情况下遵循.
例如,当您要求用户输入数字时,您可以转换使用int()
该输入可能引发数字的输入ValueError
.您可以通过简单地要求用户再次尝试来轻松恢复,因此捕获ValueError
并再次提示用户将是一个合适的计划.另一个例子是,如果您想从文件中读取某些配置,并且该文件恰好不存在.因为它是一个配置文件,您可能会将某些默认配置作为回退,因此该文件不是必需的.因此,捕捉FileNotFoundError
并简单地应用默认配置将是一个很好的计划.现在,在这两种情况下,我们都有一个非常具体的例外情况,并且有一个同样具体的计划可以从中恢复.因此,在每种情况下,我们只明确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块中做任何特殊的事情,所以这很好.但当然,有人可能会争辩说,我们至少要向用户显示一些错误消息,告诉他为什么他必须重复输入.
在许多其他情况下,只是传入except
一个标志,我们并没有真正为我们捕获的异常做好准备.除非这些例外很简单(例如ValueError
或者TypeError
),并且我们可以通过的原因很明显,所以尽量避免传递.如果真的没什么可做的(而且你完全确定它),那么考虑添加评论为什么会这样; 否则,展开except块以实际包含一些恢复代码.
except: pass
最糟糕的罪犯是两者的结合.这意味着我们愿意抓住任何错误,尽管我们绝对没有为此做好准备,我们也没有采取任何措施.您至少想记录错误并且还可能重新加载它仍然终止应用程序(在MemoryError之后,您不太可能像正常一样继续).通过虽然不仅会使应用程序保持活力(取决于你当然捕获的地方),但也会丢弃所有信息,使得无法发现错误 - 如果你不是发现它的话,尤其如此.
所以底线是:只抓住你真正期望的并准备从中恢复的例外; 所有其他可能是你应该修复的错误,或者你不准备的事情.如果您真的不需要对它们做些什么,那么传递特定的例外就没问题了.在所有其他情况下,这只是推定和懒惰的标志.你肯定想解决这个问题.
为什么"除了:传递"一个糟糕的编程习惯?
为什么这么糟糕?
try: something except: pass
这会捕获每个可能的异常,包括GeneratorExit
,KeyboardInterrupt
和SystemExit
- 这些是您可能不想捕获的异常.它和捕捉一样BaseException
.
try: something except BaseException: pass
较旧版本的文档说:
由于Python中的每个错误都会引发异常,因此使用
except:
可能会使许多编程错误看起来像运行时问题,从而阻碍了调试过程.
如果捕获父异常类,则还会捕获所有子类.只捕获您准备处理的异常会更加优雅.
这是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
然后,您将无法something
使用Ctrl-C 中断您的块.您的程序将忽略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
但是因为您使用了最广泛的异常捕获,您的代码可能有其他根本缺陷,将尝试运行完成,浪费带宽,处理时间,设备磨损,内存不足,垃圾数据收集等.
如果其他人要求你完成以便他们可以依赖你的代码,我理解被迫只是处理一切.但是如果你愿意随着你的发展而吵闹,你将有机会纠正可能只是间歇性地出现的问题,但那将是长期代价高昂的错误.
通过更精确的错误处理,您可以更加健壮地编写代码.
>>> import this
Tim Peters的Python之禅
美丽胜过丑陋.
显式优于隐式.
简单比复杂更好.
复杂比复杂更好.
Flat优于嵌套.
稀疏优于密集.
可读性很重要.
特殊情况不足以打破规则.
虽然实用性胜过纯洁.
错误不应该默默地传递.
除非明确沉默.
面对模棱两可,拒绝猜测的诱惑.
应该有一个 - 最好只有一个 - 明显的方法来做到这一点.
虽然这种方式起初可能并不明显,除非你是荷兰人.
现在比永远好.
虽然从来没有经常好过正确的现在.
如果实施很难解释,这是一个坏主意.
如果实现很容易解释,那可能是个好主意.
命名空间是一个很棒的主意 - 让我们做更多的事情吧!
所以,这是我的意见.每当您发现错误时,您应该做一些事情来处理它,即将其写入日志文件或其他内容.至少,它告诉你曾经有过错误.
通常,您可以将以下三种类别中的任何错误/异常分类:
致命:不是你的错,你无法阻止它们,你无法从中恢复.您当然不应该忽略它们并继续,并使您的程序处于未知状态.只是让错误终止你的程序,你无能为力.
Boneheaded:你自己的错,很可能是由于疏忽,错误或编程错误.你应该修复这个bug.同样,你绝对不应该忽视并继续.
外生:在特殊情况下可以预期这些错误,例如找不到文件或终止连接.你应该明确地处理这些错误,只有这些错误.
在所有情况下,except: pass
只会使程序处于未知状态,从而导致更多损坏.
这里的主要问题是它忽略了所有错误:内存不足,CPU正在烧毁,用户想要停止,程序要退出,Jabberwocky正在查杀用户.
这太过分了.在你的脑海中,你在想"我想忽略这个网络错误".如果出现意外情况,那么您的代码将以静默方式继续并以完全不可预测的方式中断,无人可以调试.
这就是为什么你应该限制自己只忽略一些错误而让其余错误通过.
该except:pass
构造基本上使在try:
块中覆盖的代码运行时出现的任何和所有异常条件静音.
这种不良做法的原因在于它通常不是你真正想要的.更常见的是,某些特定的情况即将出现,你想要保持沉默,而且except:pass
是一种过于粗暴的工具.它将完成工作,但它也会掩盖您可能没有预料到的其他错误情况,但可能非常希望以其他方式处理.
这在Python中特别重要的是,通过这种语言的习语,异常不一定是错误.当然,它们通常以这种方式使用,就像在大多数语言中一样.但是Python特别偶尔使用它们来实现某些代码任务的替代退出路径,这些代码任务实际上并不是正常运行情况的一部分,但是在大多数情况下甚至可能会出现这种情况.SystemExit
已经被提到作为一个古老的例子,但现在最常见的例子可能是StopIteration
.以这种方式使用异常引起了很多争议,特别是当迭代器和生成器首次引入Python时,但最终这个想法占了上风.
您应该至少使用except Exception:
以避免捕获系统异常,如SystemExit
或KeyboardInterrupt
.这是docs 的链接.
通常,您应该明确定义要捕获的异常,以避免捕获不需要的异常.你应该知道你忽略了什么例外.
第一个原因已经被陈述 - 它隐藏了你没想到的错误.
(#2) - 这使得其他人难以阅读和理解您的代码.如果在尝试读取文件时捕获到FileNotFoundException,那么对于其他开发人员来说,"catch"块应该具有哪些功能.如果您没有指定异常,那么您需要额外的注释来解释该块应该做什么.
(#3) - 它演示了懒惰的编程.如果使用通用try/catch,则表示您不了解程序中可能存在的运行时错误,或者您不知道Python中可能存在哪些异常.捕获特定错误表明您了解程序和Python抛出的错误范围.这更有可能使其他开发人员和代码审阅者信任您的工作.
首先,它违反了Python的两个原则:
显式优于隐式
错误不应该默默地传递
这意味着,你是故意让你的错误无声地通过.而且,你没有事件知道,确实发生了哪个错误,因为except: pass
会捕获任何异常.
其次,如果我们试图从Python的禅宗中抽象出来,并且仅仅说出理智,那么你应该知道,使用这种方法except:pass
会让你在你的系统中没有任何知识和控制.经验法则是如果发生错误则引发异常,并采取适当的措施.如果你事先不知道,应该采取什么行动,至少在某处记录错误(最好重新提出异常):
try: something except: logger.exception('Something happened')
但是,通常,如果你试图捕捉任何异常,你可能做错了!
简单地说,如果抛出异常或错误,那就错了.它可能不是非常错误,但仅仅为了使用goto语句而创建,抛出和捕获错误和异常并不是一个好主意,而且很少这样做.99%的时间,某处出现了问题.
需要解决的问题.就像在生活中一样,在编程中,如果你只是单独留下问题并试图忽略它们,它们不仅会自行消失很多次; 相反,他们变得越来越大.为了防止问题在你身上长大并再次在路上再次打击,你要么1)消除它并在之后清理乱七八糟,要么2)包含它并随后清理乱七八糟.
只是忽略异常和错误,让它们像这样是一种体验内存泄漏,出色的数据库连接,不必要的文件权限锁定等的好方法.
在极少数情况下,问题是如此微不足道,琐碎,并且 - 除了需要尝试...捕获块 - 自包含,之后确实没有任何混乱被清理.这是最佳实践不一定适用的唯一场合.根据我的经验,这通常意味着无论代码在做什么都基本上是小事和可行的,重试尝试或特殊消息之类的东西既不复杂也不值得坚持.
在我的公司,规则是几乎总是在catch块中做一些事情,如果你什么都不做,那么你必须总是发表评论,其中有很好的理由.当有任何事情要做时,你绝不能传递或留下空的阻挡块.
在我看来,错误有理由出现,我的声音愚蠢,但就是这样.良好的编程只会在您必须处理错误时引发错误.另外,正如我前段时间读到的那样,"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块也可能导致代码,其结构处理特殊类型的异常.
从字面上执行伪代码甚至不会给出任何错误:
try: something except: pass
好像它是一段完全有效的代码,而不是抛出一个代码NameError
.我希望这不是你想要的.
那么,这段代码产生了什么输出?
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
块是对复杂对象层次结构的数百行调用,并且本身在大型程序的调用树中调用.当程序出错时,你从哪里开始寻找?