什么是异常
异常是一种影响程序运行的事件。当发生超出程序规则之外的事情时,程序就会“一脸懵逼”而卡在那里,严重的程序甚至直接选择奔溃。
异常的抛出机制:
- 如果在运行时发生异常,解释器会查找相应的处理语句(称为handler).
- 要是在当前函数里没有找到的话,它会将异常传递给上层的调用函数,看看那里能不能处理。
- 如果在最外层(全局“main”)还是没有找到的话,解释器就会退出,同时打印出traceback以便让用户找到错误产生的原因。
异常的类型
下表转载于http://www.runoob.com/python/python-exceptions.html
异常名称 | 描述 |
---|---|
BaseException | 所有异常的基类 |
SystemExit | 解释器请求退出 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
Exception | 常规错误的基类 |
StopIteration | 迭代器没有更多的值 |
GeneratorExit | 生成器(generator)发生异常来通知退出 |
StandardError | 所有的内建标准异常的基类 |
ArithmeticError | 所有数值计算错误的基类 |
FloatingPointError | 浮点计算错误 |
OverflowError | 数值运算超出最大限制 |
ZeroDivisionError | 除(或取模)零 (所有数据类型) |
AssertionError | 断言语句失败 |
AttributeError | 对象没有这个属性 |
EOFError | 没有内建输入,到达EOF 标记 |
EnvironmentError | 操作系统错误的基类 |
IOError | 输入/输出操作失败 |
OSError | 操作系统错误 |
WindowsError | 系统调用失败 |
ImportError | 导入模块/对象失败 |
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 | 用户代码生成的警告 |
异常的捕获和处理
异常就像跑得飞起的兔子,在兔子屁股后面狂追一通只会劳心劳力。正确的方法是使用陷阱去捕获“兔子”,而常用的捕获异常的陷阱分两类:
try 捕获有Python或程序本身引发的异常
raise 手工引发一个异常
try...except...else
try:
statement1
except <name>: # 异常名字name
statement2
except <name>,<value>: # 获取异常的附加数据
statement3
else:
statement # 如果无异常则执行该部分语句
这个过程的原理很简单,打个比方:
try就好比你往外撒了一个渔网,如果有“鱼”(异常),就进入到except,这部分就好比根据鱼的种类做不同的动作,如果没有鱼,就执行else部分。
下面是往一个没有写入权限的文件写数据的例子,此时会发生文件流错误IOError。
try:
file = open('testfile', 'w')
file.write('一个测试异常的文件')
except IOError:
print('Error: 未找到文件或文件不存在')
else:
print('写入操作成功')
file.close()
执行以上代码,结果如下:
Error: 未找到文件或文件不存在
使用except不带异常类型
如果你不知道程序运行过程肯能会发生什么异常的名字,可以直接以except捕获所有可能的异常。当然这个方式就无法识别出具体的异常信息。
try:
statement1
except:
statement2
else:
statement3
try...finally
try-finally语句是无论是否发生异常,都将执行finally部分的代码,通常用于文件的关闭等动作。可以配合except、else使用。
try:
statement1
except:
statement2
else:
statement3
finally:
statement4
继续以上述文件写入的操作为例
try:
file = open("testfile", "w")
try:
file.write("这是一个测试异常的文件")
finally:
print("关闭文件")
file.close()
except IOError:
print("Error: 未找到文件或读取文件失败")
当在try块中抛出一个异常,立即执行finally块代码。
finally块中的所有语句执行后,异常被再次触发,并执行except块代码。
异常的参数
上面讲到try...except...else时,except除了异常类型name,还有异常参数value。通常包含错误字符串,错误数据,错误位置等信息。
try:
statement1
except ExceptionType as e:
return e.argument
触发异常
使用raise语句触发异常
raise [Exception [, args [, traceback]]]
语句中Exception是异常的类型(例如,NameError)参数是一个异常参数值。该参数是可选的,如果不提供,异常的参数是"None"。
最后一个参数是可选的(在实践中很少使用),如果存在,是跟踪异常对象。
def mtest(str):
if str == 'Hello':
raise NameError('无效输入')
if str.isdigit():
raise TypeError('数据类型错误')
try:
#mtest('1')
mtest('Hello')
except NameError as e:
print(e)
except TypeError as e:
print(e)
else:
print('Done')
为了捕获多个异常,除了上面所示的声明多个except语句外,还可以在一个except语句后将多个异常列成一个元组,例如:
except (zeroDivisionError, TypeError)
自定义异常类型
下面是从Exception类派生的一个自定义异常类,重写了默认的__init__()异常。
class MyError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
try:
raise MyError(2*2)
except MyError as e:
print('Exception occured, value:', e.value)
断言assert
assert False,'error...'
print 'continue'
这个语句,先判断assert后面紧跟的语句是True还是False。如果是True则执行print,如果是False则中断程序,调用默认的异常处理器,同时输出assert语句逗号后面的提示信息。上述例子程序会中断,提示error,后面的print不执行。
with...as
我们平时在使用类似文件的流对象时,使用完毕后要调用close方法关闭,很麻烦。
这里with…as语句提供了一个非常方便的替代方法:open打开文件后将返回的文件流对象赋值给f,然后在with语句块中使用。with语句块完毕之后,会隐藏地自动关闭文件。
如果with语句或语句块中发生异常,会调用默认的异常处理器处理,但文件还是会正常关闭。
with open('test.txt', 'r') as f:
f.read()
print(2/0)
print('continue')
其他说明
- 在2.x时代,所有类型的对象都是可以被直接抛出的,在3.x时代,只有继承自BaseException的对象才可以被抛出。
- 2.x raise语句使用逗号将抛出对象类型和参数分开,3.x取消了这种奇葩的写法,直接调用构造函数抛出对象即可。
- 在2.x时代,异常在代码中除了表示程序错误,还经常做一些普通控制结构应该做的事情,在3.x中可以看出,设计者让异常变的更加专一,只有在错误发生的情况才能去用异常捕获语句来处理。
- 捕获一个通用异常时,在Exception和BaseException之间,建议使用Exception。虽然BaseException包含了Exception,但是Exception所包含的范围就足够了。