本章主題:
什么是異常
Python中的異常
探測(cè)和處理異常
上下文管理
引發(fā)異常
斷言
標(biāo)準(zhǔn)異常
創(chuàng)建異常
相關(guān)模塊
什么是異常
- 錯(cuò)誤
從軟件方面來(lái)說(shuō),錯(cuò)誤是語(yǔ)法或是邏輯上的。語(yǔ)法錯(cuò)誤只是軟件的結(jié)構(gòu)上有錯(cuò)誤,導(dǎo)致不能被解釋器解釋或者編譯器無(wú)法編譯。這些錯(cuò)誤必須在程序執(zhí)行前被糾正。
當(dāng)程序的語(yǔ)法正確之后,剩下的就是邏輯錯(cuò)誤了。邏輯錯(cuò)誤可能是由于不完整或是不合法的輸入所致,在其他情況下,還可能是邏輯無(wú)法生成、計(jì)算、或是輸出結(jié)果需要的過(guò)程無(wú)法執(zhí)行。這些錯(cuò)誤通常被稱為域錯(cuò)誤和范圍錯(cuò)誤。
當(dāng)Python檢測(cè)到一個(gè)錯(cuò)誤時(shí),解釋器會(huì)指出當(dāng)前流已經(jīng)無(wú)法繼續(xù)執(zhí)行下去。這個(gè)時(shí)候就出現(xiàn)異常。
- 異常
對(duì)異常最好的描述是:它是因?yàn)槌绦虺霈F(xiàn)了錯(cuò)誤而在正??刂屏饕酝獠扇〉男袨?。這個(gè)行為又分為兩個(gè)階段:首先是引起異常發(fā)生的錯(cuò)誤,然后是檢測(cè)(和采取可能的措施)階段。
python中的異常
- NameError :嘗試訪問(wèn)一個(gè)未申明的變量
>>> foo
Traceback (innermost last):
File "<stdin>",line 1, in ?
NameError: name 'foo' is not defined
#NameError 表示我們?cè)L問(wèn)了一個(gè)沒(méi)有初始化的變量。在Python解釋器的符號(hào)表沒(méi)有找到那個(gè)令人討厭的變量,我們將在后面的兩章討論名稱空間,現(xiàn)在大家可以認(rèn)為它們是連接名字和對(duì)象的“地址簿”就可以了
#任何可訪問(wèn)的變量必須在名稱空間里列出,訪問(wèn)變量需要由解釋器進(jìn)行搜索,如果請(qǐng)求的名字沒(méi)有在任何名稱空間里找到,那么將會(huì)生成一個(gè)NameError異常
- ZeroDivisionError : 除數(shù)為零
>>> 1/0
Traceback (inner last):
File"<stdin>",line 1,in ?
ZeroDivisionError: integer division or modulo by zero
#任何數(shù)值被零除都會(huì)導(dǎo)致一個(gè)ZeroDivisionError異常
- SyntaxError: Python 解釋器語(yǔ)法錯(cuò)誤
>>> for
File "<string>", line 1
for
^
SyntaxError: invalid syntax
#SyntaxError 異常時(shí)唯一不是在運(yùn)行時(shí)發(fā)生的異常。它代表Python
代碼中有一個(gè)不正確的結(jié)構(gòu),在它改正之前程序是無(wú)法執(zhí)行的。這些錯(cuò)誤一般都是在編譯時(shí)發(fā)生,Python解釋器無(wú)法把你腳本轉(zhuǎn)化為python字節(jié)碼。也有可能是你導(dǎo)入了一個(gè)有缺陷的模塊。
- IndexError : 請(qǐng)求的索引超出序列范圍
>>> alist = []
>>> alist[0]
Traceback (innermost last):
File "<stdin>", line 1,in ?
IndexError: last index out of range
#IndexError 在你嘗試使用一個(gè)超出范圍的值索引序列引發(fā)。
- KeyError :請(qǐng)求一個(gè)不存在的字典的關(guān)鍵字
>>> aDict = {'host': 'earth','port': 80}
>>> print aDict['server']
Traceback (innermost last):
File "<stdin>", line 1, in ?
KeyError: server
#映射對(duì)象,例如字典,是依靠關(guān)鍵字(key)訪問(wèn)數(shù)據(jù)的,如果使用了錯(cuò)誤的或者是不存在的鍵請(qǐng)求字典會(huì)引發(fā)一個(gè)KeyError異常。
- IOError : 輸入/輸出錯(cuò)誤
>>> f = open ("blah")
Traceback (innermost last):
File "<stdin>",line 1, in ?
IOError: [Errno 2] No such file or directory: 'blah'
#類似嘗試打開一個(gè)不存在的磁盤文件一類的操作會(huì)引起一個(gè)操作系統(tǒng)輸入/輸出(I/O)錯(cuò)誤。任何類型的I/O錯(cuò)誤都會(huì)引發(fā)IOError異常
- AttributeError : 嘗試訪問(wèn)未知的對(duì)象屬性
>>> class myClass(object) :
... pass
...
>>> myInst = myClass()
>>> myInst.bar = 'spam'
>>> myInst.bar
'spam'
>>> myInst.foo
Traceback (innermost last):
File "<stdin>", line 1, in ?
AttributeError foo
#在這個(gè)例子里,我們?cè)趍yInst.bar存儲(chǔ)了一個(gè)值,也就是實(shí)例myInst的bar屬性,屬性被定義后,我們可以使用梳洗的點(diǎn)屬性操作符訪問(wèn)它,但如果是沒(méi)有定義屬性,例如我們?cè)L問(wèn)foo屬性,將導(dǎo)致一個(gè)AttributeError異常。
檢測(cè)和處理異常
異常可以通過(guò)try語(yǔ)句來(lái)檢測(cè)。任何在try語(yǔ)句塊里的代碼都會(huì)被檢測(cè),檢查有無(wú)異常發(fā)生。
try語(yǔ)句有二種形式: try-except和try-finally 這兩個(gè)語(yǔ)句是互斥的,也就是說(shuō)你只能使用其中的一種。一個(gè)try語(yǔ)句可以對(duì)應(yīng)一個(gè)或者多個(gè)except子句,但只能對(duì)應(yīng)一個(gè)finally子句,或者是一個(gè)try-except-finally復(fù)合語(yǔ)句。
try-except語(yǔ)句
try:
try_suite #監(jiān)控這里的異常
except Exception[,reason]:
except_suite #異常處理代碼
>>> try:
... f = open ('blah','r')
... except IOError,e:
... print 'could not open file:',e
...
cloud not open file: [Errno 2] No such file or directory
處理多個(gè)異常的except語(yǔ)句
#我們還可以在一個(gè)except子句里處理多個(gè)異常。except語(yǔ)句在處理多個(gè)異常要求異常被放在一個(gè)元祖里:
except (Exception1,Exception2)[,reason]:
suite_for_Exception1_and_Exception2
def safe_float(object):
try:
retval = float(object)
except (ValueError,TypeError):
retval = 'argument must be a number or numberic string'
return retval
捕獲所有異常
#way1
#
try:
:
except Exception, e:
#error occurred,log 'e',etc.
#way2
#
try:
:
except:
#error occurred,etc
#關(guān)于捕獲所有異常,你應(yīng)當(dāng)知道有些異常不是由錯(cuò)誤條件引起的,它們是SystemExit和KeyboardInterupt。
#SystemExit是由于當(dāng)前Python應(yīng)用程序需要退出
#KeyboardInterrupt是代表用戶按下了Ctrl+C,想要退出Python。在真正需要的時(shí)候,這些異常卻會(huì)被異常捕獲。
異常被遷移到新式類(new-style class)啟用了一個(gè)新的“所有異常之母”,這個(gè)類叫做 BaseException,異常的繼承結(jié)構(gòu)有了少許的調(diào)整,為了讓人們擺脫不得不除創(chuàng)建兩個(gè)處理器慣用法。
- BaseException
| - KeyboardInterrupt
| - SystemExit
| - Exception
| - (all other current bulit-in exception) 所有內(nèi)建異常
try:
:
except Exception, e:
#handle real errors
#如果你確實(shí)要捕獲所有異常,那么你需要使用新的 BaseException:
try:
:
except BaseException, e:
#handle all errors
異常參數(shù)
異常也可以有參數(shù),異常引發(fā)后它會(huì)被傳遞給異常處理器。當(dāng)異常被引發(fā)后參數(shù)是作為附加幫助信息傳遞給異常處理器的。雖然異常原因是可選的,但標(biāo)準(zhǔn)內(nèi)建異常提供至少一個(gè)參數(shù),指示異常原因的一個(gè)字符串。
異常的參數(shù)可以在處理器里忽略,但是Python提供了保存這個(gè)值得語(yǔ)法。我們已經(jīng)在上邊接觸到相關(guān)內(nèi)容:要想訪問(wèn)提供的異常原因,你必須保留一個(gè)變量來(lái)保存這個(gè)參數(shù)。把這個(gè)參數(shù)放在except語(yǔ)句后,接在要處理的異常后面。except語(yǔ)句的這個(gè)語(yǔ)法可以被擴(kuò)展為:
# single exception
except Exception[,reason]:
suite_for_Exception_with_Argument
# multiple exception
except (Exception1,Exception2,...,ExceptionN)[,reason]:
suite_for_Exception1_to_ExceptionN_with_Argument
#reason將會(huì)是一個(gè)包含來(lái)自導(dǎo)致異常的代碼的診斷信息的類實(shí)例。異常參數(shù)自身會(huì)組織一個(gè)元組,并存儲(chǔ)為類實(shí)例(異常類的實(shí)例)的屬性
無(wú)論reason只包含一個(gè)字符串或是由錯(cuò)誤編號(hào)和字符串組成的元組,調(diào)用str(reason)總會(huì)返回一個(gè)良好可讀的錯(cuò)誤原因。不要忘記reason是一個(gè)類實(shí)例——這樣做你其實(shí)是調(diào)用類的特殊方法str()
信用卡交易系統(tǒng)(cardrun.py)
#!/usr/bin/env python
def safe_float(obj):
'safe version if float()'
try:
retval = float(obj)
except (ValueError,TypeError), diag:
retval = str(diag)
return retval
def main():
'handle 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('account 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()
else 子句
我們已經(jīng)看過(guò)else語(yǔ)句段配合其他python語(yǔ)句,比如條件和循環(huán)。至于try-except語(yǔ)句段,它的功能和你所見過(guò)的其他else沒(méi)有太多的不同: 在try范圍中沒(méi)有異常被檢測(cè)到時(shí),執(zhí)行else子句
import 3rd_party_module
log = open('logfile','w')
try:
3rd_party_module.function()
except:
log.write("*** caught exception in module \n")
else:
log.write("*** no exceptions caught\n")
log.close()
finally 子句
finally 子句是無(wú)論異常是否發(fā)生,是否捕捉都會(huì)執(zhí)行的一段代碼
下面是try-except-else-finally的語(yǔ)法實(shí)例:
try:
A
except MyException:
B
else:
C
finally:
D
#無(wú)論如何,你都可以有不止一個(gè)的except子句,但最少有一個(gè)except語(yǔ)句,而else和finally都是可選的。無(wú)論異常發(fā)生在A、B或C都 將執(zhí)行finally塊。
通過(guò)try-finally來(lái)實(shí)現(xiàn)關(guān)閉文件而無(wú)論錯(cuò)誤是否發(fā)生
#方式1
try:
try:
ccfile = open('carddata.txt')
txns = ccfile.readlines()
except IOError:
log.write('no txns this month\n')
finally:
ccfile.close()
#方式2
try:
try:
ccfile = open('carddata.txt')
txns = ccfile.readlines()
finally:
ccfile.close()
except IOError:
log.write('no txns this month\n')
上下文管理
-
with語(yǔ)句
類似于 try-except-finally,with語(yǔ)句也是用來(lái)簡(jiǎn)化代碼的,這與try-except和try-finally所想達(dá)到的目的前呼后應(yīng)。try-except和try-finally的一種特定的配合用法是保證共享資源的唯一分配,并在任務(wù)結(jié)束后釋放它。比如文件(數(shù)據(jù)、日志、數(shù)據(jù)庫(kù)等等)、線程資源、簡(jiǎn)單同步、數(shù)據(jù)庫(kù)連接,等等。
然而,with語(yǔ)句的目的在于從流程圖中把try、except、finally關(guān)鍵字和資源分配釋放相關(guān)代碼統(tǒng)統(tǒng)去掉,而不是像try-except-finally那樣僅僅簡(jiǎn)化代碼使之易用
#with語(yǔ)法的基本語(yǔ)法如下:
with context_expr [as var]:
with_suite
創(chuàng)建異常(myexc.py)
#!/usr/bin/env python
#
import os
import socket
import errno
import types
import 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(myarg)
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 'rmx':
if os.access(file, permd[eachPerm])
perms += eachPerm
else:
perm += '-'
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], perms)
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 = (error.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 = 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 = open(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()