[PYTHON] 核心編程筆記之十-Py

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 monthn')

       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… processedn')

       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 modulen")

else:

   log.write("*** no exception caughtn")

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 monthn')

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 monthn')

如果出於一些原因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 monthn')

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 monthn')

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包含各種異常相關的對象和函數