对于Python异常处理慎用“except:pass”建议


Posted in Python onApril 02, 2015

翻译自StackOverflow中一个关于Python异常处理的问答。

问题:为什么“except:pass”是一个不好的编程习惯?

我时常在StackOverflow上看到有人评论关于except: pass的使用,他们都提到这是一个不好的Python编程习惯,应该避免。可我想知道为什么?有时候我并不在意出现的错误,而是只想让我的程序继续进行下去。就像这样:
 

try:
  something
except:
  pass

为什么这么使用except:pass不好?这背后的原因是什么,是不是因为这样我会放掉一些本该被处理的错误?还是这样我会捕获到所有类型的错误?

最佳回答:

正如你所猜测的那样,这么做的确有两个不好的地方。首先,因为没有指定任何异常类型,所以会捕获到任何类型的错误。其次,捕获到错误之后只会简单地让它通过而不是采取必要的处理措施。

我接下来的解释或许会有点长,所以将重点总结如下:
1. 不要将任意类型的错误作为捕获对象。必须明确你想要捕获的错误类型,并且写明只捕获它们。
2. 不要试图简单地敷衍错误处理动作。除非这么做是有目的的,但这通常都不太好。

那么接下来让我们更深入一些:

不要将任意异常作为捕获目标

当在代码中的某个地方使用异常捕获语句块时,你通常知道这个地方可能会抛出异常,并且你也知道这个地方可能会发生什么样的问题进而抛出何种异常,一旦异常被抛出,你将捕获到这个异常并使程序回到正轨上来。这就意味着你一定对这种异常有所准备,并能够在它发生的时候及时采取措施进行处理。

举个例子,你需要用户输入一个数字,并且使用int()函数将用户输入的字符串转换为整数类型,这时候你一定会想到如果输入的字符串并不是数字,那么就会发生值错误(ValueError)。如果真的发生了错误,那么你可以通过简单的让用户重新输入来让程序回到正轨,所以捕获值错误以及促使用户重新输入就是一个比较合理的处理策略。再举一个例子,如果你想从一个文件中读取配置信息,但正巧这个文件不存在。那么因为这是一个配置文件,如果它不存在你会返回一些默认的配置选项,所以这个文件就不是这么必要了。在这个例子中,捕获文件未找到错误(FileNotFoundError)以及返回默认配置项则是一个比较合适的处理策略。通过以上两个例子可以看到,我们都是在等待捕获特定的错误,并且针对每种错误都有特定的处理策略。

然而,如果我们在这里捕获所有的异常,那么为特定异常准备的那些处理策略就会因为遇到非正常类型的异常而失效,这将会使得正常的程序流程中断并且无法恢复。

让我们还是举配置文件的那个例子。正常的处理策略是如果发现文件并不存在,我们将使用默认的配置项,并可能在稍后决定是否将当前的配置项自动保存为配置文件(这样的话下一次文件就肯定存在了)。现在让我们假定我们捕获到了一个IsADirectoryError或是PermissionError错误,在这种情况下,我们可能不想让程序继续执行,我们仍然能够使用默认的配置参数,但是随后我们就不能保存文件了。也有可能用户希望使用自定义的配置项,所以这样的话就不能使用默认配置项了。所以我们这时候可能需要立即告知用户并停止当前程序。也有可能我们并不想在这么一小块代码中做这么多的事情,而是让应用层面的部分去关心它,所以我们也可能让这个错误浮到顶层,让顶层的业务逻辑去处理。

在Python 2 idioms document文档中也提到了一个简单的例子:如果在我们的代码中出现了一个简单的拼写错误而导致程序错误。在这种情况下因为我们捕获所有的异常,所以我们将会捕获到名称错误(NameErrors)以及语法错误(SyntaxErrors)。两者都是常见的错误,并且两者都是不希望出现在我们最终代码中的。但是因为我们什么异常都逮,当异常发生时我们将无法区分具体的错误类型并且无法进行调试。

但是也存在这样一些并未预先准备的危险异常,诸如系统错误(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块中的passing语句显示了我们并没有真正的对异常做好准备。除非是一些简单的异常(诸如值错误ValueError或类型错误TypeError)我们都应该做一些操作,原因也很明显,避免简单的passing。如果真没什么可做的(如果你真的确定),那么考虑加一些解释性的注释在此;否则,请扩展except块添加一些恢复性的代码。

except: pass

最不能容忍的就是两者的结合了。这意味着我们自愿捕获任何异常(包括那些我们没有准备的)并且对它们视而不见。你应该至少在日志中记录一下这个错误并且向上提出来终止当前程序(我就不信出现MemoryError你的程序依然能正常运行)。放过这些异常将会使程序在错误的轨道上继续运行下去并且丢掉了关键的错误信息从而使得错误不易被发现,特别是当不是你亲自遇到它的时候。

所以,底线是去捕获那些经过准备的特定异常;其他发生的异常要么是等着你去修复的错误,要么是你无法处理的。当真的没有什么可做的时候放过某些特定异常是可以的,其他情况如果这么做就只能被认为是怠工或偷懒了。你的确应该去处理这些异常的。

Python 相关文章推荐
详细解析Python中的变量的数据类型
May 13 Python
Python构造自定义方法来美化字典结构输出的示例
Jun 16 Python
python抓取并保存html页面时乱码问题的解决方法
Jul 01 Python
Python2.7基于淘宝接口获取IP地址所在地理位置的方法【测试可用】
Jun 07 Python
python实现批量修改文件名代码
Sep 10 Python
python3实现163邮箱SMTP发送邮件
May 22 Python
python使用writerows写csv文件产生多余空行的处理方法
Aug 01 Python
PyCharm取消波浪线、下划线和中划线的实现
Mar 03 Python
python利用opencv实现SIFT特征提取与匹配
Mar 05 Python
Python中openpyxl实现vlookup函数的实例
Oct 28 Python
解决hive中导入text文件遇到的坑
Apr 07 Python
Python+Matplotlib+LaTeX玩转数学公式
Feb 24 Python
Python的设计模式编程入门指南
Apr 02 #Python
介绍Python中的一些高级编程技巧
Apr 02 #Python
用Python代码来解图片迷宫的方法整理
Apr 02 #Python
在Python3中使用asyncio库进行快速数据抓取的教程
Apr 02 #Python
Python中的Classes和Metaclasses详解
Apr 02 #Python
详解Python中的装饰器、闭包和functools的教程
Apr 02 #Python
详解Python的迭代器、生成器以及相关的itertools包
Apr 02 #Python
You might like
php错误提示failed to open stream: HTTP request failed!的完美解决方法
2011/06/06 PHP
Zend Framework教程之Zend_Db_Table_Row用法实例分析
2016/03/21 PHP
Json_decode 解析json字符串为NULL的解决方法(必看)
2017/02/17 PHP
PHP使用数组实现矩阵数学运算的方法示例
2017/05/29 PHP
js取模(求余数)隔行变色
2014/05/15 Javascript
JQuery操作元素的css样式
2015/03/09 Javascript
JavaScript实现跨浏览器的添加及删除事件绑定函数实例
2015/08/04 Javascript
JS中substring与substr的用法
2016/11/16 Javascript
图片上传之FileAPI与NodeJs
2017/01/24 NodeJs
原生javascript实现读写CSS样式的方法详解
2017/02/20 Javascript
nodejs连接mysql数据库简单封装示例-mysql模块
2017/04/10 NodeJs
VUE 使用中踩过的坑
2018/02/08 Javascript
vue2.0安装style/css loader的方法
2018/03/14 Javascript
在小程序中集成redux/immutable/thunk第三方库的方法
2018/08/12 Javascript
小程序视频或音频自定义可拖拽进度条的示例代码
2018/09/30 Javascript
Jquery和CSS实现选择框重置按钮功能
2018/11/08 jQuery
解决element-ui的下拉框有值却无法选中的情况
2020/11/07 Javascript
python MySQLdb Windows下安装教程及问题解决方法
2015/05/09 Python
实例讲解Python中global语句下全局变量的值的修改
2016/06/16 Python
利用Python抓取行政区划码的方法
2016/11/28 Python
关于反爬虫的一些简单总结
2017/12/13 Python
numpy数组拼接简单示例
2017/12/15 Python
100行Python代码实现自动抢火车票(附源码)
2018/01/11 Python
Python实现PS图像抽象画风效果的方法
2018/01/23 Python
python使用PIL给图片添加文字生成海报示例
2018/08/17 Python
Python Matplotlib实现三维数据的散点图绘制
2019/03/19 Python
python如何把字符串类型list转换成list
2020/02/18 Python
python中Django文件上传方法详解
2020/08/05 Python
python在一个范围内取随机数的简单实例
2020/08/16 Python
python利用platform模块获取系统信息
2020/10/09 Python
美国在线咖啡、茶和餐厅供应商:LollicupStore
2018/05/04 全球购物
日本乐天德国站:Rakuten.de
2019/05/16 全球购物
工厂实习感言
2014/01/14 职场文书
十一酒店活动方案
2014/02/20 职场文书
羽毛球社团活动总结
2014/06/27 职场文书
JS实现简单的九宫格抽奖
2022/06/28 Javascript