Python异常处理
1.异常定义
异常即非正常状态,在Python中使用异常对象来表示异常。若程序在编译或运行过程中发生错误,程序的执行过程就会发生改变,抛出异常对象,程序流进入异常处理。如果异常对象没有被处理或捕捉,程序就会执行回溯(Traceback)来终止程序。
- 异常的分类:
程序遇到逻辑或算法错误
运行过程中计算机错误:内存不够或者io错误
2.异常处理的定义
python解释器检测到错误,触发异常(也允许程序员自己触发异常)
程序员编写特定的代码,专门用来捕捉这个异常(这段代码与程序逻辑无关,与异常处理有关)
如果捕捉成功则进入另外一个处理分支,执行你为其定制的逻辑,使程序不会崩溃,这就是异常处理
3.异常处理的意义
python解析器去执行程序,检测到了一个错误时,触发异常,异常触发后且没被处理的情况下,程序就在当前异常处终止,后面的代码不会运行,所以你必须提供一种异常处理机制来增强你程序的健壮性与容错性 ,友好性,在程序出现bug时一般不会将错误信息显示给用户,而是出现一个提示的页面。
4.异常处理语法
(1)try..except..else
注意:except子句的数量没有限制,但使用多个except子句捕获异常时,如果异常类之间具有继承关系,则子类应该写在前面,否则父类将会直接截获子类异常。放在后面的子类异常也就不会执行。
格式
- try:
- 可能触发异常的语句块
- except [exceptionType]:
- 捕获可能触发的异常[可以指定处理的异常类型]
- except [exceptionType][,date]:
- 捕获异常并获取附加数据
- except:
- 没有指定异常类型,捕获任意异常
- else:
- 没有触发异常时,执行的语句块
执行一个try语句时,python解析器会在当前程序流的上下文中作标记,当出现异常后,程序流能够根据上下文的标记回到标记位,从而避免终止程序。
1. 如果try语句执行时发生异常,程序流跳回标记位,并向下匹配执行第一个与该异常匹配的except子句,异常处理完后,程序流就通过整个try语句(除非在处理异常时又引发新的异常)。
2. 如果没有找到与异常匹配的except子句(也可以不指定异常类型或指定同样异常类型Exception,来捕获所有异常),异常被递交到上层的try(若有try嵌套时),甚至会逐层向上提交异常给程序(逐层上升直到能找到匹配的except子句。实在没有找到时,将结束程序,并打印缺省的错误信息)。
3. 如果在try子句执行时没有发生异常,python将执行else语句后的语句(可选),然后控制流通过整个try语句
需求:将用户输入的两个数字相加
while True: num1 = raw_input('num1:') num2 = raw_input('num2:') try: num1 = int(num1) num2 = int(num2) result = num1 + num2 except Exception, e: print '出现异常,信息如下:' print e
(2)try..except..else..finally
try:
msg = input(">>")
print(int(msg))
except Exception as e:
print("异常的类型是:%s"%type(e))
print("异常的内容是:%s"%e)
else:
print('如果代码块不抛出异常会执行此行代码!')
finally:
print('不管代码块是否抛出异常都会执行此行代码!')
- 运行结果:
>>34
34
如果代码块不抛出异常会执行此行代码!
不管代码块是否抛出异常都会执行此行代码!
(3)万能异常
在python的异常中,有一个万能异常:Exception,他可以捕获任意异常,即:
1
2
3
4
5
|
s1 = 'hello'
try :
int (s1)
except Exception,e:
print e
|
既然有这个万能异常,其他异常是不是就可以忽略了???
答:当然不是,对于特殊处理或提醒的异常需要先定义,最后定义Exception来确保程序正常运行。不推荐使用
1
2
3
4
5
6
7
8
9
|
s1 = 'hello'
try :
int (s1)
except KeyError,e:
print '键错误'
except IndexError,e:
print '索引错误'
except Exception, e:
print '错误'
|
捕捉多个异常
方法一:指定一个通用异常,可以捕获多个不同的包含在Exception类中的异常类
- try:
- 语句块
- except Exception:
- 语句块
- try:
- 语句块
- except (IOError,ValueError):
- 语句块
- try:
- 语句块
- except:
- 语句块
-
except
语句不是必须的,finally
语句也不是必须的,但是二者必须要有一个,否则就没有try
的意义了。 -
except
语句可以有多个,Python会按except
语句的顺序依次匹配你指定的异常,如果异常已经处理就不会再进入后面的except
语句。 -
except
语句可以以元组形式同时指定多个异常,参见实例代码。 -
except
语句后面如果不指定异常类型,则默认捕获所有异常,你可以通过logging或者sys模块获取当前异常。 - 如果要捕获异常后要重复抛出,请使用
raise
,后面不要带任何参数或信息。 - 不建议捕获并抛出同一个异常,请考虑重构你的代码。
- 不建议在不清楚逻辑的情况下捕获所有异常,有可能你隐藏了很严重的问题。
- 尽量使用内置的异常处理语句来替换
try/except
语句,比如with
语句,getattr()
方法。
5.Python中的异常处理机制
在Python当中,若一个程序在运行的时候出错,Python解释器会自动的在出错的地方生成一个异常对象,而后Python解释器会自动的在出错地方的附近寻找有没有对这个异常对象处理的代码,所谓异常处理代码就是try……except语句,如果没有,Python解释器会自动的将这个异常对象抛给其调用函数,就这样层层抛出,如果在main当中也没有对这个异常对象处理的代码,Python解释器(实际上是操作系统)最后会做一个简单粗暴的处理,将整个程序给终止掉,并将错误的信息在显示屏上输出。6.Python中常见异常种类
python中的异常种类非常多,每个异常专门用于处理某一项异常!!!
AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x IOError 输入/输出异常;基本上是无法打开文件在,做文件操作的时候常遇到的错误,这里介绍一下FileNotFoundError 就是做文件操作的时候文件不存在 ImportError 无法引入模块或包;基本上是路径问题或名称错误 IndentationError 语法错误(的子类) ;代码没有正确对齐,缩进问题 IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5] KeyError 试图访问字典里不存在的键 KeyboardInterrupt Ctrl+C被按下 NameError 使用一个还未被赋予对象的变量 SyntaxError Python 语法错误,少了个冒号啊,没有空格啊,都会出现这个错误。 TypeError 传入对象类型与要求的不符合 UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量, 导致你以为正在访问它 ValueError 传入一个调用者不期望的值,即使值的类型是正确的
python所有的标准异常类:
异常名称 | 描述 |
---|---|
BaseException | 所有异常的基类 |
SystemExit | 解释器请求退出 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
Exception | 常规错误的基类 |
StopIteration | 迭代器没有更多的值 |
GeneratorExit | 生成器(generator)发生异常来通知退出 |
SystemExit | Python 解释器请求退出 |
StandardError | 所有的内建标准异常的基类 |
ArithmeticError | 所有数值计算错误的基类 |
FloatingPointError | 浮点计算错误 |
OverflowError | 数值运算超出最大限制 |
ZeroDivisionError | 除(或取模)零 (所有数据类型)在做数据处理和计算的时候会遇到这种错误 |
AssertionError | 断言语句失败 |
AttributeError | 对象没有这个属性 |
EOFError | 没有内建输入,到达EOF 标记 |
EnvironmentError | 操作系统错误的基类 |
IOError | 输入/输出操作失败 |
OSError | 操作系统错误 |
WindowsError | 系统调用失败 |
ImportError | 导入模块/对象失败 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
LookupError | 无效数据查询的基类 |
IndexError | 序列中没有没有此索引(index) |
KeyError | 映射中没有这个键 |
MemoryError | 内存溢出错误(对于Python 解释器不是致命的) |
NameError | 未声明/初始化对象 (没有属性) |
UnboundLocalError | 访问未初始化的本地变量 |
ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
RuntimeError | 一般的运行时错误 |
NotImplementedError | 尚未实现的方法 |
SyntaxError | Python 语法错误 |
IndentationError | 缩进错误 |
TabError | Tab 和空格混用 |
SystemError | 一般的解释器系统错误 |
TypeError | 对类型无效的操作 |
ValueError | 传入无效的参数 |
UnicodeError | Unicode 相关的错误 |
UnicodeDecodeError | Unicode 解码时的错误 |
UnicodeEncodeError | Unicode 编码时错误 |
UnicodeTranslateError | Unicode 转换时错误 |
Warning | 警告的基类 |
DeprecationWarning | 关于被弃用的特征的警告 |
FutureWarning | 关于构造将来语义会有改变的警告 |
OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
PendingDeprecationWarning | 关于特性将会被废弃的警告 |
RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
SyntaxWarning | 可疑的语法的警告 |
UserWarning | 用户代码生成的警告 |
7.主动触发异常
如果你需要自主抛出异常一个异常,可以使用raise
关键字,等同于C#和Java中的throw
,其语法规则如下。
raise NameError("bad name!")
格式:raise [exceptionType[,argument][,traceback]]
- def testRaise(number):
- if number < 1:
- raise ValueError('Invalid value') #或者 raise ValueError,'Invalid value'
- testRaise(0)
raise
关键字后面可以指定你要抛出的异常实例,一般来说抛出的异常越详细越好,Python在exceptions
模块内建了很多的异常类型,通过使用dir()
函数来查看exceptions
中的异常类型,如下:
import exceptions
print dir(exceptions)
# ['ArithmeticError', 'AssertionError'...]
当然你也可以查阅Python的文档库进行更详细的了解。
执行raise语句时,python会创建指定异常类的对象,还能够指定对异常对象进行初始化的参数,参数也可以为由若干参数组成的元组。
注意:一旦执行raise语句,程序就会被终止。
1
2
3
4
|
如果输入的数据不是整数,则引发一个ValueError
假设输入1.2,运行结果为: please input a int data :1.2 如果输入1,运行结果为: please input a int data :1 |
8.打印异常信息
def Mysql_select(db_host,sql): try: cnx = pymysql.connect(host='192.168.1.15',port= 3306,user = 'root',passwd='123123',db='test',timeout=5) # except TypeError: # print 'In typeerror' except Exception as e: print repr(e)
as获取异常信息
每个异常都会有一定的描述信息,可以通过as关键字来获取。但是这种异常信息并不适合一般用户阅读,所以会使用自定义的异常信息。但是仍然会将原有的异常信息保留起来,用于后期的异常分析
- #!/usr/bin/env python
- try:
- try:
- openFile = open('notExistsFile.txt','r')
- fileContent = openFile.readlines()
- except (IOError,ValueError) as info: #或者except (IOError,ValueError),info:
- print info
- except:
- print 'process exception'
- else:
- print 'Reading the file'
- In [164]: %run testError.py
- [Errno 2] No such file or directory: 'notExistsFile.txt'
- Reading the file
异常参数
也可以使用异常参数作为输出的异常信息参数,来获取异常信息。并且异常参数中包含有异常信息、错误数字、错误位置等属性
- #!/usr/bin/env python
- try:
- try:
- openFile = open('notExistsFile.txt','r')
- fileContent = openFile.readlines()
- except (IOError,ValueError),info:
- print dir(info)
- print info.args
- except:
- print 'process exception'
- else:
- print 'Reading the file'
- In [44]: %run test.py
- ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__getslice__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__unicode__', 'args', 'errno', 'filename', 'message', 'strerror']
- (2, 'No such file or directory')
- Reading the file
traceback追踪异常
使用traceback追踪异常的时候,需要import traceback模块。traceback模块可以有效的帮助查看异常的详细信息。
注意:若希望获取异常的详细信息,却又不会终止程序的执行,可以在except子句中使用tarceback.print_exc()函数。
tarceback.print_exc():
print_exc(limit=None, file=None)
Shorthand for ‘print_exception(sys.exc_type, sys.exc_value, sys.exc_traceback, limit, file)’.
(In fact, it uses sys.exc_info() to retrieve the same information in a thread-safe way.)
输出sys.exc_type, sys.exc_value, sys.exc_traceback, limit, file等异常信息,实际上是以线程安全的方式去使用sys.exc_info()函数来获取相同的信息
- #!/usr/bin/env python
- import traceback
- try:
- openFile = open('notExistsFile.txt','r')
- fileContent = openFile.readlines()
- except IOError as info:
- print 'File not Exists'
- print info
- traceback.print_exc()
- print 'continue'
- except:
- print 'process exception'
- else:
- print 'Reading the file'
- In [38]: %run test.py
- File not Exists
- [Errno 2] No such file or directory: 'notExistsFile.txt'
- Traceback (most recent call last):
- File "/usr/local/src/pyScript/test.py", line 5, in <module>
- openFile = open('notExistsFile.txt','r')
- IOError: [Errno 2] No such file or directory: 'notExistsFile.txt'
- continue
- #!/usr/bin/env python
- import traceback
- try:
- with open('notExistsFile.txt','r') as openFile:
- fileContent = openFile.readlines()
- except IOError:
- with open('errorLog','w+') as errorInfo:
- traceback.print_exc(file=errorInfo)
- print 'continue'
- except:
- print 'process exception'
- else:
- print 'Reading the file'
- In [61]: %run test.py
- continue
- In [62]: cat errorLog
- Traceback (most recent call last):
- File "/usr/local/src/pyScript/test.py", line 5, in <module>
- with open('notExistsFile.txt','r') as openFile:
- IOError: [Errno 2] No such file or directory: 'notExistsFile.txt'
traceback.print_exc()函数实际上是call sys.exc_info()
- import sys
- try:
- a=b
- b=c
- except:
- info=sys.exc_info()
- print info[0],":",info[1]
- In [65]: %run test.py
- <type 'exceptions.NameError'> : name 'b' is not defined
1、str(e)
返回字符串类型,只给出异常信息,不包括异常信息的类型,如1/0的异常信息
‘__init__() got an unexpected keyword argument 'timeout'’
2、repr(e)
给出较全的异常信息,包括异常信息的类型,如1/0的异常信息
TypeError("__init__() got an unexpected keyword argument 'timeout'",)
3、e.message
获得的信息同str(e)
4、采用traceback模块
需要导入traceback模块,此时获取的信息最全,与python命令行运行程序出现错误信息一致。使用traceback.print_exc()打印异常信息到标准错误,就像没有获取一样,或者使用traceback.format_exc()将同样的输出获取为字符串。你可以向这些函数传递各种各样的参数来限制输出,或者重新打印到像文件类型的对象。
9.自定义异常类型
Python中自定义自己的异常类型非常简单,只需要要从Exception
类继承即可(直接或间接):
class SomeCustomException(Exception):
pass
class AnotherException(SomeCustomException):
pass
一般你在自定义异常类型时,需要考虑的问题应该是这个异常所应用的场景。如果内置异常已经包括了你需要的异常,建议考虑使用内置的异常类型。比如你希望在函数参数错误时抛出一个异常,你可能并不需要定义一个InvalidArgumentError
,使用内置的ValueError
即可。
自定义的异常类只能通过raise关键字来手动触发
- class testError(Exception): #直接集成Exception类
- def __init__(self,arg):
- self.args = arg
- try:
- raise testError('Just test')
- except testError,info:
- print info.args
- In [52]: %run test.py
- ('J', 'u', 's', 't', ' ', 't', 'e', 's', 't')
class
WupeiqiException(Exception):
def
__init__(
self
, msg):
self
.message
=
msg
def
__str__(
self
):
return
self
.message
try
:
raise
WupeiqiException(
'我的异常'
)
except
WupeiqiException,e:
print
e
with..as触发异常自动关闭资源
在使用类文件的流对象时,都需要单独的调用close()来关闭资源。with..as语句能够实现在with语句块执行完后,自动的关闭文件。如果with语句块中触发异常,会调用默认的异常处理器处理,而且文件仍然能够正常关闭
- #!/usr/bin/env python
- import os
- def testWith(fileName):
- try:
- with open(fileName,'r+') as pwd:
- pwd.readlines()
- print 2/0
- except Exception:
- print 'File closed:',pwd.closed #判断文件是否关闭
- if __name__ == '__main__':
- if os.path.exists('/usr/local/src/pyScript/fileOperation.txt'):
- testWith('/usr/local/src/pyScript/fileOperation.txt')
- print 'continue'
- In [17]: %run test.py
- File closed: True #没有call close()函数,文件仍然自动关闭。
- continue
10.传递异常 re-raise Exception
捕捉到了异常,但是又想重新抛出它(传递异常),使用不带参数的raise
语句即可:
def f1():
print(1/0)
def f2():
try:
f1()
except Exception as e:
raise # don't raise e !!!
f2()
在Python2中,为了保持异常的完整信息,那么你捕获后再次抛出时千万不能在raise
后面加上异常对象,否则你的trace
信息就会从此处截断。以上是最简单的重新抛出异常的做法,也是推荐的做法。
还有一些技巧可以考虑,比如抛出异常前你希望对异常的信息进行更新。
def f2():
try:
f1()
except Exception as e:
e.args += ('more info',)
raise
如果你有兴趣了解更多,建议阅读这篇博客。
Python3对重复传递异常有所改进,你可以自己尝试一下,不过建议还是遵循以上规则。
11.Exception 和 BaseException
当我们要捕获一个通用异常时,应该用Exception
还是BaseException
?我建议你还是看一下 官方文档说明,这两个异常到底有啥区别呢? 请看它们之间的继承关系。
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration...
+-- StandardError...
+-- Warning...
从Exception
的层级结构来看,BaseException
是最基础的异常类,Exception
继承了它。BaseException
除了包含所有的Exception
外还包含了SystemExit
,KeyboardInterrupt
和GeneratorExit
三个异常。
由此看来你的程序在捕获所有异常时更应该使用Exception
而不是BaseException
,因为被排除的三个异常属于更高级别的异常,合理的做法应该是交给Python的解释器处理。
except Exception as e和 except Exception, e
代码示例如下:
try:
do_something()
except NameError as e: # should
pass
except KeyError, e: # should not
pass
在Python2的时代,你可以使用以上两种写法中的任意一种。在Python3中你只能使用第一种写法,第二种写法已经不再支持。第一个种写法可读性更好,而且为了程序的兼容性和后期移植的成本,请你果断抛弃第二种写法。
raise "Exception string"
把字符串当成异常抛出看上去是一个非常简洁的办法,但其实是一个非常不好的习惯。
if is_work_done():
pass
else:
raise "Work is not done!" # not cool
上面的语句如果抛出异常,那么会是这样的:
Traceback (most recent call last):
File "/demo/exception_hanlding.py", line 48, in <module>
raise "Work is not done!"
TypeError: exceptions must be old-style classes or derived from BaseException, not str
这在 Python2.4 以前是可以接受的做法,但是没有指定异常类型有可能会让下游没办法正确捕获并处理这个异常,从而导致你的程序难以维护。简单说,这种写法是是封建时代的陋习,应该扔了。
使用内置的语法范式代替try/except
Python 本身提供了很多的语法范式简化了异常的处理,比如for
语句就处理了的StopIteration
异常,让你很流畅地写出一个循环。
with
语句在打开文件后会自动调用finally
并关闭文件。我们在写 Python 代码时应该尽量避免在遇到这种情况时还使用try/except/finally的思维来处理。
# should not
try:
f = open(a_file)
do_something(f)
finally:
f.close()
# should
with open(a_file) as f:
do_something(f)
再比如,当我们需要访问一个不确定的属性时,有可能你会写出这样的代码:
try:
test = Test()
name = test.name # not sure if we can get its name
except AttributeError:
name = 'default'
其实你可以使用更简单的getattr()
来达到你的目的。
name = getattr(test, 'name', 'default')
最佳实践
最佳实践不限于编程语言,只是一些规则和填坑后的收获。
- 只处理你知道的异常,避免捕获所有异常然后吞掉它们。
- 抛出的异常应该说明原因,有时候你知道异常类型也猜不出所以然。
- 避免在
catch
语句块中干一些没意义的事情,捕获异常也是需要成本的。 - 不要使用异常来控制流程,那样你的程序会无比难懂和难维护。
- 如果有需要,切记使用
finally
来释放资源。 - 如果有需要,请不要忘记在处理异常后做清理工作或者回滚操作。