10.1 什么是异常
10.1.1 错误
错误时语法或是逻辑上的,语法错误指示软件的结构上有错误,导致不能被解释器解释或编译器无法编译
当Python检测到一个错误时,解释器就会支出当前流已经无法继续执行下去,这时就出现了异常
10.1.2 异常
10.2 Python中的异常
例:
NameError: 尝试访问一个未声明的变量
>>> foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined
除数为零:
>>> 1/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
Python解释器语法错误:
>>> for
File "<stdin>", line 1
for
^
SyntaxError: invalid syntax
请求的索引超出序列范围:
>>> aList = []
>>> aList[0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
请求一个不存在的字典关键字:
>>> aDict = {'host':'earth','port':80}
>>> print aDict['server']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'server'
输入/输出错误
>>> f = open('haha')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: 'haha'
尝试访问未知的对象属性
>>> class myClass(object):
... pass
...
>>> myInst = myClass()
>>> myInst.bar = 'spam'
>>> myInst.bar
'spam'
>>> myInst.foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'myClass' object has no attribute 'foo'
10.3 检测和处理异常:
异常可以通过try语句来检测,任何在try语句块里的代码都会被检测,检查有无异常发生
try语句有两种形式:
try-except和try-finally
一个try语句可以对应一个或多个except子句,但只能对应一个finally子句,或一个try-except-finally复合语句
10.3.1 try-except 语句
try:
try_suite # watch for exceptions here 监控这里的异常
except Exception[,reason]:
except_suite # exception-handling code 异常处理代码
例:
>>> try:
... f = open('haha','r')
... except IOError,e:
... print 'could not open file:',e
...
could not open file: [Errno 2] No such file or directory: 'haha'
10.3.2 封装内建函数
>>> float(12345)
12345.0
>>> float('12345')
12345.0
>>> float('123.45e67')
1.2345e+69
>>> float('foo')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: could not convert string to float: foo
>>> float(['this is',1,'list'])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: float() argument must be a string or a number
如果参数类型正确,但值不可转换为浮点数,那么将引发ValueError异常
安全的调用float()函数:
我们创建一个封装函数,safe_float(),第一次改进中我们搜索并忽略ValueError
>>> def safe_float(obj):
... try:
... return float(obj)
... except ValueError:
... pass
...
>>> safe_float('haha')
>>> safe_float('123')
123.0
以上不足在于出现错误无返回任何信息,我们至少应该显式的返回None
>>> def safe_float(obj):
... try:
... retval = float(obj)
... except ValueError:
... retval = 'None'
... return retval
...
>>> safe_float('123')
123.0
>>> safe_float('haha')
'None'
>>>def safe_float(obj):
... try:
... retval = float(obj)
... except ValueError:
... retval = 'could not convert non-number to float'
... return retval
...
>>> safe_float('123')
123.0
>>> safe_float('bad input')
'could not convert non-number to float'
但如果传递一个非法对象,还是会出问题
>>> safe_float({'a':'Dict'})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in safe_float
TypeError: float() argument must be a string or a number
10.3.3 带有多个except的try语句
except Exception1[, reason]:
suite_for_exception_Exception1
except Exception2[, reason]:
suite_for_exception_Exception2
例:
>>> def safe_float(obj):
... try:
... retval = float(obj)
... except ValueError:
... retval = 'could not convert non-number to float'
... except TypeError:
... retval = 'object type cannot be converted to float'
... return retval
...
使用错误的参数调用这个函数:
>>> safe_float('xyz')
'could not convert non-number to float'
>>> safe_float(())
'object type cannot be converted to float'
>>> safe_float(200L)
200.0
>>> safe_float(45.67000)
45.670000000000002
10.3.4 处理多个异常的except语句:
except (Exception1,Exception2)[, reason]:
suite_for_exception_Exception1_and_Exception2
except (Exc1[,Exc2[, ... ExcN]])[, reason]:
suite_for_exceptions_Exc1_and_ExcN
要求safe_float()函数中的所有异常必须使用同样的代码:
>>> def safe_float(obj):
... try:
... retval = float(obj)
... except(ValueError,TypeError):
... retval = 'argument must be a number or numeric string'
... return retval
...
现在,错误的输出会返回相同字符串:
>>> safe_float('Spanish Inquisition')
'argument must be a number or numeric string'
>>> safe_float([])
'argument must be a number or numeric string'
>>> safe_float('1.6')
1.6000000000000001
>>> safe_float(1.6)
1.6000000000000001
>>> safe_float(932)
932.0
10.3.5 捕获所有异常:
try:
:
except Exception,e:
# error,occurred,log 'e',etc
不推荐:
try:
:
except Exception,e:
# error,occurred,etc.
捕获Python需要退出的异常:
try:
:
except(KeyboardInterupt,SystemExit):
# user wants to quit
raise # reraise back to caller
except Exception:
# handle real errors
当你有了一个Exception处理器后,你不必为这两个异常创建额外的处理器
try:
:
except Exception,e:
# handle real errors
如果你确实需要捕获所有异常,那么你就得使用新的BaseExcption:
try:
:
except BaseException,e:
# handle all errors
注: 不要处理并忽略所有错误
try:
large_block_of_code #bandage of large piece of code
except Exception: # same as except:
pass # blind eye ignoring all errors
10.3.6 异常参数:
# single exception
except Exception[, reason]:
suite_for_Exception_with_Argument
# multiple exceptions
except (Exception1,Exception2,...,ExceptionN)[, reason]:
suite_for_Exception1_to_ExceptionN_wih_Argument
例:传参给内建float函数一个无效对象,引发TypeError异常:
>>> try:
... float(['float() does not','like lists', 2])
... except TypeError,diag: # capture diagnostic info
... pass
...
>>> type(diag)
<type 'exceptions.TypeError'>
>>> print diag
float() argument must be a string or a number
我们首先在一个try语句块中引发一个异常,随后简单的忽略了这个异常,但保留了错误的信息,调用内置type()函数,我们可以确认我们的异常的确是TypeError异常类的实例,最后我们队异常诊断参数调用print以显示错误
为了获取更多关于异常的信息,我们可以调用该实例的__class__属性,它标示了实例是从什么类实例化而来,类对象也有属性
>>> diag
TypeError('float() argument must be a string or a number',)
>>> diag.__class__
<type 'exceptions.TypeError'>
>>> diag.__class__.__doc__
'Inappropriate argument type.'
>>> diag.__class__.__name__
'TypeError'
我们用字符串化(string representation)的异常参数来替换单一的错误信息
>>> def safe_float(object):
... try:
... retval = float(object)
... except(ValueError, TypeError), diag:
... retval = str(diag)
... return retval
当我们提供的safe_float()参数给的不恰当时,虽然只有一条捕获语句,但可以获得如下信息:
>>> safe_float('xyz')
'could not convert string to float: xyz'
>>> safe_float({})
'float() argument must be a string or a number'
10.3.7 在应用使用我们封装的函数:
我们将在一个迷你应用中特地的使用这个函数,它将打开信用卡交易数据文件,加载所有交易,包括解释的字符串,下面是一个示例的carddate.txt文件:
# cat carddata.txt
# carddata.txt previous balance
25
debits
21.64
541.24
25
credits
-25
-541.24
finance charge/late fees
7.30
5
# vi cardrun.py
----------------------------
#!/usr/bin/env python
def safe_float(obj):
'safe version of float()'
try:
retval = float(obj)
except(ValueError,TypeError),diag:
retval = str(diag)
return retval
def main():
'handles all the data processing'
log = open('cardlog.txt','w')
try:
ccfile = open('carddata.txt','r')
except IOError,e:
log.write('no txns this month\n')
log.close()
return
txns = ccfile.readlines()
ccfile.close()
total = 0.00
log.write('accout log:\n')
for eachTxn in txns:
result = safe_float(eachTxn)
if isinstance(result,float):
total += result
log.write('data... processed\n')
else:
log.write('ignored: %s' %result)
print '$%.2f(new balance)' % (total)
log.close()
if __name__ == '__main__':
main()
----------------------------
# python cardrun.py
-------------------------
$58.94(new balance)
---------------------------
# cat cardlog.txt
------------------------------
accout log:
ignored: could not convert string to float: # carddata.txt previous balance
data... processed
ignored: could not convert string to float: debits
data... processed
data... processed
data... processed
ignored: could not convert string to float: credits
data... processed
data... processed
ignored: could not convert string to float: finance charge/late fees
data... processed
data... processed
ignored: could not convert string to float:
----------------------------------
10.3.8 else 子句
在try范围中没有异常被检测到时,才会执行else子句
import 3rd_party_module
log = open('logfile.txt','w')
try:
3rd_party_module.function()
except:
log.write("*** caught exception in module\n")
else:
log.write("*** no exception caught\n")
log.close()
10.3.9 finally子句
try-except-else-finally语法示例:
try:
A
except MyException: B
else: C
finally: D
10.3.10 try-finally语句:
无论try中是否有异常触发,finally代码段都会被执行
try:
try_suite
finally:
finally_suite # 无论如何都执行
当在try范围中产生一个异常时,会立即跳转到finally语句段,当finally所有代码执行完毕,才会继续向上一层引发异常
try:
cofile = open('carddata.txt')
except IOError:
log.write('no txns this month\n')
txns = cofile.readlines()
ccfie,close
但有很多原因会导致readlines()失败,其中一种就是carddata.txt存在于网络(或软盘上),本身介质的不稳定导致不能稳定读取
我们可以把这一小段读取数据的代码整个放入try子句范围中:
try:
cofile = open('carddata.txt')
txns = cofile.readlines()
ccfie.close
except IOError:
log.write('no txns this month\n')
如果出于一些原因readlines()调用失败,异常处理会去继续执行except中的子句,从而不会去关闭文件(ccfie.close)
如何在出现错误后,仍旧可以关闭文件,我们可以通过try-finally来实现:
ccfile = None
try:
try:
cofile = open('carddata.etc')
txns = cofile.readlines()
ccfie.close
except IOEorror:
log.write('no txns this month\n')
finally:
if ccfile:
ccffle.close()
以下代码本质与之前干的同样的工作,区别在于关闭文件发生在异常处理器将错误写入日志之前,这是因为finally会自动重新引发异常
ccfile = None
try:
try:
cofile = open('carddata.etc')
txns = cofile.readlines()
finally:
if ccfile:
ccffle.close()
except IOError:
log.write('no txns this month\n')
10.3.11 try-except-else-finally
try:
try_suite
except Exception1:
suite_for_Exception1
except (Exception2,Exception3,Exception4):
suite_for_Exceptions_2_3_and_4
except Exception5,Argument5:
suite_for_excetion5_plus_argument
except (Exception6,Exception7),Argument67:
suite_for_excetion6_and_7_plus_argument
except:
suite_for_all_other_exceptions
else:
no_exceptions_detected_suite
finally:
always_execute_suite
10.4 上下文管理
10.4.1 with语句
with context_expr [as var]:
with_suite
例:
with open('/etc/passwd','r') as f:
for eachLine in f:
# ...do stuff with eachLine or f...
10.4.2 *上下文管理协议
10.5 *字符串作为异常
10.6 触发异常
到目前为止,我们所见到的异常都是由解释器引发的,由于执行期间的错误而引发,程序员在编写API时也希望在遇到错误的输入时触发异常,为此,Python提供了一种机制让程序员明确的触发异常:这就是raise语句:
10.6.1 raise语句
raise [SomeException [, args[, traceback]]]
raise语句的用法
rasie 语法描述
raise exclass触发一个异常,从exclass生成一个实例(不含任何异常参数)
raise exclass()同上,除了现在不是类;通过函数调用操作符作用于类名生成一个新的exclass实例,同样也没有异常参数
raise exclass,args同上,但同时提供的异常参数args,可以是一个参数也可以元祖
raise exclass(args)同上
raise exclass,args, tb同上,但提供一个追踪对象tb供使用
raise exclass,instance通过实例触发异常
raise instance通过实例触发异常
raise string触发字符串异常
raise string,args触发伴随着args
raise string,args,tb同上,但提供一个追踪对象tb供使用
raise重新触发前一个异常,如果之前没有异常,触发TypeError
10.7 断言
断言是一句必须等价于布尔真的判定,此外,发生异常也意味着表达式为假
可以理解为是raise-if-not语句,如果返回值是假,触发异常
10.7.1 断言语句
assert expression[, arguments]
assert用法:
assert 1 == 1
assert 2 +2 == 2 * 2
assert len(['my list', 12]) < 10
assert range(3) == [0, 1, 2]
AssertionError异常和其他异常一样可以用try-except语句块捕捉,如果没有捕捉,它将终止程序运行而且提供一个如下的traceback:
>>> assert 1 == 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
我们可以提供一个异常参数给我们的assert命令:
>>> assert 1 == 0 , 'One dose not equal zero silly!'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: One dose not equal zero silly!
用try-except语句捕获AssertionError异常:
>>> try:
... assert 1 == 0, 'One does not equal zero silly!'
... except AssertionError,args:
... print '%s: %s' %(args.__class__.__name__, args)
...
AssertionError: One does not equal zero silly!
例:
def assert(expr, args=None):
if __debug__ and not expr:
raise AssertionError,args
10.8 标准异常:
表10.2 列出了所有的Python当前的标准异常集,所有的异常都是内建的,所以它们在脚本启动前或在互交命令行提示符出现时已经是可用的了
表10.2 Python内建异常
(略)
10.9 创建异常:
例:
--------------------------------------
#!/usr/bin/env python
import os,socket,errno,types,tempfile
class NetworkError(IOError):
pass
class FileError(IOError):
pass
def updArgs(args,newarg=None):
if isinstance(args,IOError):
myargs = []
myargs.extend([arg for arg in args])
else:
myargs = list(args)
if newarg:
myargs.append(newarg)
return tuple(myargs)
def fileArgs(file, mode, args):
if args[0] == errno.EACCES and 'access' in dir(os):
perms = ''
permd = {'r': os.R_OK, 'w': os.W_OK, 'x': os.X_OK}
pkeys = permd.keys()
pkeys.sort()
pkeys.reverse()
for eachPerm in 'rwx':
if os.access(file, permd[eachPerm]):
perms += eachPerm
else:
perms += '-'
if isinstance(args,IOError):
myargs = []
myargs.extend([arg for arg in args])
else:
myargs = list(args)
myargs[1] = "'%s' %s (perms: '%s')" %(mode,myargs[1],perm)
myargs.append(args.filename)
else:
myargs = args
return tuple(myargs)
def myconnect(sock,host,port):
try:
sock.connect((host,port))
except socket.error, args:
myargs = updArgs(args)
if len(myargs) == 1:
myargs = (errno,ENXIO, myargs[0])
raise NetworkError, updArgs(myargs, host + ':' + str(port))
def myopen(file,mode='r'):
try:
fo = open(file,mode)
except IOError,args:
raise FileError, fileArgs(file, mode, args)
return fo
def testfile():
file = tempfile.mktemp()
f = open(file,'w')
f.close()
for eachTest in ((0, 'r'), (0100, 'r'),(0400,'w'),(0500, 'w')):
try:
os.chmod(file, eachTest[0])
f = myopen(file, eachTest[1])
except FileError, args:
print "%s: %s" %(args.__class__.__name__, args)
else:
print file, "opened ok... perm ignored"
f.close()
os.chmod(file,0777)
os.unlink(file)
def testnet():
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
for eachHost in ('deli', 'www'):
try:
myconnect(s, 'deli', 8080)
except NetworkError, args:
print "%s: %s" %(args.__class__.__name__, args)
if __name__ == '__main__':
testfile()
testnet()
--------------------------------------
10.10 为什么用异常(现在)?
10.11 到底为什么要异常?
10.12 异常和sys模块
>>> try:
... float('abc123')
... except:
... import sys
... exc_tuple = sys.exc_info()
...
>>> print exc_tuple
(<type 'exceptions.ValueError'>, ValueError('could not convert string to float: abc123',), <traceback object at 0x7f1412e09fc8>)
>>>
>>> for eachItem in exc_tuple:
... print eachItem
...
<type 'exceptions.ValueError'>
could not convert string to float: abc123
<traceback object at 0x7f1412e09fc8>
我们从sys.exc_info()得到的元祖中是:
exc_type: 异常类
exc_value: 异常类的实例
exc_traceback: 追踪对象
10.13 相关模块
模块描述
exceptions内建异常(永远不用导入这个模块)
contectliba为使用with语句的上下文对象工具
sys包含各种异常相关的对象和函数