1. 程式人生 > >Python基礎:23異常

Python基礎:23異常

一:概述

        1:錯誤

錯誤有語法和邏輯上的區別。語法錯誤指示軟體的結構上有錯誤,導致不能被直譯器解釋或編譯器無法編譯。這些錯誤必須在程式執行前糾正。邏輯錯誤可能是由於不完整或是不合法的輸入所致,還可能是邏輯上無法生成,計算,或是輸出結果需要的過程無法執行。

當Python 檢測到一個錯誤時,直譯器就會指出當前流已經無法繼續執行下去。這時候就出現了異常。

2:異常

對異常的最好描述是: 它是因為程式出現了錯誤而在正常控制流以外採取的行為. 這個行為又分為兩個階段: 首先是錯誤引發異常, 然後是檢測,並採取可能的措施。

第一個階段是在出現錯誤後發生的. 只要檢測到錯誤並且意識到異常條件,直譯器會引發一個異常.

Python 也允許程式設計師自己引發異常. 無論是 Python 直譯器還是程式設計師引發的, 異常就是錯誤發生的訊號. 當前流將被打斷, 之後就是處理這個錯誤並採取相應的操作.便是第二階段。

二:Python中的異常舉例

訪問變數需要由直譯器進行搜尋,如果請求的名字沒有在任何名稱空間裡找到, 那麼將會生成一個 NameError 異常.

>>> foo

Traceback (innermost last): File "<stdin>", line 1, in ?

NameError: name 'foo' is not defined

ZeroDivisionError: 除數為零

>>> 1/0

Traceback (innermost last): File "<stdin>", line 1, in ?

ZeroDivisionError: integer division or modulo by zero

SyntaxError: Python 直譯器語法錯誤

>>> for

File "<string>", line 1

for

^

SyntaxError: invalid syntax

SyntaxError 異常是唯一不是在執行時發生的異常. 它代表 Python 程式碼中有一個不正確的結構, 在它改正之前程式無法執行。

IndexError:請求的索引超出序列範圍

>>> aList = []

>>> aList[0]

Traceback (innermost last): File "<stdin>", line 1, in ?

IndexError: list index out of range

KeyError:請求一個不存在的字典關鍵字

>>> aDict = {'host': 'earth', 'port': 80}

>>> print aDict['server'] Traceback (innermost last):

File "<stdin>", line 1, in ?

KeyError: server

IOError: 輸入/輸出錯誤,比如嘗試開啟一個不存在的磁碟檔案一類的操作會引發一個作業系統輸入/輸出(I/O)錯誤.

>>> f = open("blah")

Traceback (innermost last):

File "<stdin>", line 1, in ?

IOError: [Errno 2] No such file or directory: 'blah'

AttributeError: 嘗試訪問未知的物件屬性

>>> class myClass(object):

... pass

...

>>> myInst = myClass()

>>> myInst.foo

Traceback (innermost last): File "<stdin>", line 1, in ?

AttributeError: foo

三:檢測和處理異常

異常可以通過try 語句來檢測. 任何在 try 語句塊裡的程式碼都會被監測有無異常發生。

一個 try語句可以對應一個或多個 except 子句, 但只能對應一個finally 子句, 或是一個try-except-finally 複合語句。

try-finally 只允許檢測異常並做一些必要的清除工作(無論發生錯誤與否), 沒有任何異常處理設施。

還可以新增一個可選的 else 子句,處理沒有探測到異常的時執行的程式碼。

1:try-except

try-except 語句(以及其更復雜的形式),進行異常監控,並且提供了處理異常的機制。語法如下:

try:

try_suite                       #監控此處的異常

except  Exception[, reason]:

except_suite                #異常處理程式碼

例子如下:

try:

f = open('blah', 'r')

except IOError, e:

print 'could not open file:', e

    結果是:

could not open file: [Errno 2] No such file or directory

在開啟一個不存在的檔案時仍然發生了 IOError .加入了探測和處理異常的程式碼. 當引發 IOError異常時, 告訴直譯器讓它打印出一條診斷資訊. 而後程式繼續執行, 而不像以前的例子那樣直接退出。

在程式執行時,直譯器嘗試執行 try 塊裡的所有程式碼, 如果程式碼塊完成後沒有異常發生, 執行流就會忽略 except 語句繼續執行. 而當 except 語句所指定的異常發生後, 儲存錯誤的原因, 控制流立即跳轉到對應的處理器,而try 子句的剩餘語句將被忽略。

在上邊的例子中,只捕獲 IOError 異常. 任何其他異常不會被我們指定的處理器捕獲。

try 語句塊中異常發生點後的剩餘語句永遠不會執行,一旦一個異常被引發, 就必須決定控制流下一步到達的位置,直譯器將搜尋處理器, 一旦找到, 就開始執行處理器中的程式碼。

如果沒有找到合適的處理器, 那麼異常就向上移交給呼叫者去處理, 這意味著堆疊框架立即回到之前的那個. 如果在上層呼叫者也沒找到對應處理器, 該異常會繼續被向上移交, 直到找到合適處理器. 如果到達最頂層仍然沒有找到對應處理器, 那麼就認為這個異常是未處理的, Python 直譯器會顯示出跟蹤返回訊息, 然後退出。

2:帶多個except的try子句

可以把多個 except 語句連線在一起, 處理一個 try 塊中可能發生的多種異常, 如下所示:

try:

try_suite

except  Exception1[, reason1]:

suite_for_exception_Exception1

except  Exception2[, reason2]:

suite_for_exception_Exception2

......

首先嚐試執行 try 子句, 如果沒有錯誤, 忽略所有的 except 從句繼續執行. 如果發生異常, 直譯器將在這一串處理器(except 子句)中查詢匹配的異常.如果找到對應的處理器,執行流將跳轉到這裡。比如:

def  safe_float(obj):

try:

retval = float(obj)

except  ValueError:

retval = 'could notconvert non-number to float'

except  TypeError:

retval = 'objecttype cannot be converted to float'

return  retval

使用錯誤的引數呼叫這個函式, 我們得到下面的輸出結果:

>>> safe_float('xyz')

'could not convert non-number to float'

>>> safe_float(())

'argument must be a string'

3:處理多個異常的except語句

還可以在一個except 子句裡處理多個異常。將多個異常被放在一個元組裡:

try:

try_suite

except (Exc1[, Exc2[, ... ExcN]])[, reason]:

suite_for_exceptions_Exc1_to_ExcN

    例子如下:

def  safe_float(obj):

try:

retval = float(obj)

except  (ValueError,  TypeError):

retval = 'argumentmust be a number or numeric string'

return  retval

現在, 錯誤的輸入會返回相同的字串:

4:捕獲所有異常

自版本 1.5 後, 異常成為類,因而現在有一個異常繼承結構可以遵循。根據異常繼承的樹結構,Exception 是在最頂層的, 所以可以捕獲所有的異常:

try:

......

except  Exception, e:

# error occurred, log 'e', etc.

另一種不太推薦的方法是使用裸 except 子句:

try:

......

except:

# error occurred, etc.

這個語法不如前一個 "Pythonic" 。它不是好的 Python 程式設計樣式。

另外,有些異常不是由於錯誤條件引起的。它們是 SystemExit 和KeyboardInterupt。 SystemExit是由於當前Python 應用程式需要退出, KeyboardInterupt 代表使用者按下了 CTRL-C , 想要關閉 Python。

在 Python 2.5中,異常被遷移到了 new-styleclass 上。啟用了一個新的"所有異常的基類",這個類叫做 BaseException , 異常的繼承結構有了少許調整。KeyboardInterrupt和 SystemExit 被從Exception 裡移出, 和Exception 平級:

- BaseException

 |- KeyboardInterrupt

 |- SystemExit

 |- Exception

 |- (all other current built-in exceptions) 所有當前內建異常

因此,如果需要捕獲所有異常,需要下面的語法:

try:

......

except  (KeyboardInterupt,  SystemExit):

# user wants to quit

raise

except  Exception:

# handle real errors

    或者是:

try:

......

except  BaseException, e:

# handle all errors

注意,一個不正確的使用方法就是把一大段程式放入一個 try 塊中, 再用一個通用的except 語句“過濾”掉任何致命的錯誤並忽略它們:

# this isreally bad code

try:

large_block_of_code             

except  Exception:

pass                       

要避免像上面那樣,把大片的程式碼裝入 try-except 中然後使用 pass 忽略掉錯誤。

5:異常引數

異常也可以有引數, 異常引發後它會被傳遞給異常處理器。異常的引數可以在處理器裡忽略, 但 Python 提供了儲存這個值的語法:

# single  exception

except  Exception[, reason]:

suite_for_Exception_with_Argument

# multiple  exceptions

except  (Exception1, Exception2,..., ExceptionN)[, reason]:

suite_for_Exception1_to_ExceptionN_with_Argument

reason是異常類的例項,該例項有個屬性,名為args,args是個元組,其中包含了該異常的提示資訊:

def  safe_float(obj):

       try:

              retval = float(obj)

       except  ValueError,e:

              print'type of e is ', type(e)

              print'e is ', e

              print'e.args is ', e.args

              retval = 'could notconvert non-number to float'

       except  TypeError,e:

              print'type of e is ', type(e)

              print'e is ', e

              print'e.args is ', e.args

              retval = 'object typecannot be converted to float'

       return  retval

>>> a = safe_float([])

type of e is <type 'exceptions.TypeError'>

e is  float()argument must be a string or a number

e.args is ('float() argument must be a string or a number',)

>>> a = safe_float('abc')

type of e is <type 'exceptions.ValueError'>

e is  could notconvert string to float: abc

e.args is  ('couldnot convert string to float: abc',)

對於大多內建異常, args元組只包含一個指示錯誤原因的字串。作業系統或其他環境型別的錯誤, 例如 IOError , 元組中會把作業系統的錯誤編號放在錯誤字串前。

無論 reason.args 只包含一個字串或是由錯誤編號和字串組成的元組, 呼叫 str(reason) 總會返回一個良好可讀的錯誤原因。reason 是一個類例項,因此本質上還是呼叫類的特殊方法 __str__() .

其實,上面的例子可以簡化為下面的程式碼:

def  safe_float(obj):

       try:

              retval = float(obj)

       except  (ValueError, TypeError),e:

              retval = str(e)

       return  retval

>>> safe_float('xyz')

'could not convert string to float: xyz'

>>> safe_float({})

'float() argument must be a string or a number'

6:else子句

else除了可以配合條件和迴圈之外,還可以與try-except 語句段配合,它的功能和其他else 沒有太多的不同:如果在try範圍中的所有程式碼都完全成功,也就是在結束前沒有引發異常。則執行在else 範圍中的程式碼。

例子如下:

log = open('logfile.txt', 'w')

try:

function()

except:

log.write("*** caughtexception in module\n")

else:

log.write("*** no exceptionscaught\n")

log.close()

根據執行時是否引發異常,在日誌中寫入不同的訊息。

7:finally 子句

finally 子句是無論異常是否發生都會執行的一段程式碼。從Python 2.5 開始, 可以用finally 子句與try-except 或try-except-else 一起使用。下面是try-except-else-finally語法的示例:

try:

A

except  MyException:

B

else:

C

finally:

D

try語句可以有不止一個的except 子句,但最少有一個except 語句,而else 和finally 都是可選的。上述程式碼可能的順序是A-C-D[正常]或A-B-D[異常]。無論異常發生在A,B,和/或C,都將執行finally 塊。

另一種使用finally 的方式是finally 單獨和try 連用。這個try-finally 語句和try-except區別在於它不是用來捕捉處理異常的,而是用來在無論異常是否發生,都要維持一致的行為的情況。因為無論try中是否有異常觸發,finally 程式碼段都會被執行

try:

try_suite

finally:

finally_suite #無論如何都執行

當在try 範圍中產生一個異常時,則將該異常暫存,然後立即跳轉到finally 語句段。當finally 中的所有程式碼都執行完畢後,如果有儲存的異常,則會繼續向上一層引發異常。但是如果在finally中,引發了另一個異常,或者呼叫了return或break語句,則會丟棄儲存的異常。

比如開啟檔案,讀檔案的操作:

ccfile = None

try:

try:

ccfile =open('carddata.txt', 'r')

txns = ccfile.readlines()

finally:

if  ccfile:

ccfile.close()

except  IOError:

log.write('no txns thismonth\n')

注意,無論選擇什麼語法,都至少要有一個except 子句,而else 和finally 都是可選的。

四:觸發異常

可以通過raise語句,明確的觸發異常。

raise [SomeException [, args [, traceback]]]

如果raise後沒有引數,則raise重新丟擲當前作用域中最後一個啟用的異常。如果當前作用域中沒有啟用的異常,則丟擲TypeError異常以表示這是一個錯誤(如果在IDLE中執行,則會丟擲Queue.Empty異常)。

raise還可以跟三個引數,前兩個引數用於決定異常的型別和值。raise最常用的方式是其後跟一個類,不需要其他引數。

如果第一個引數是一個類,那麼它將成為異常的型別。第二個引數用於決定異常的值:如果它是類的例項,那麼該例項將成為異常的值。如果第二個引數是一個元組,它用於類建構函式的引數列表;如果它是None,則使用一個空的引數列表。

如果存在第三個引數且不為None,那麼它必須是一個回溯物件,且它將替換當前異常發生的位置。如果存在第三個引數且值不是回溯物件或者None,將會丟擲TypeError異常。

如果第一個引數是一個例項,那麼將該例項的類作為異常的型別,而例項本身則作為異常的值,第二個引數必須是None。也就是: raise instance等價於raise instance.__class__, instance

五:斷言

斷言是一句必須等價於布林真的判定:測試一個表示式,如果返回值是假,觸發AssertionError(斷言錯誤)的異常。如果斷言成功則不採取任何措施。斷言通過assert 語句實現,在1.5 版中引入。

AssertionError 異常和其他的異常一樣可以用try-except語句塊捕捉,但是如果沒有捕捉,它將終止程式執行而且提供一個如下的traceback:

>>> assert  1 == 0

Traceback (innermost last):

File "<stdin>", line 1, in ?

AssertionError

可以提供一個異常引數給assert 命令:

>>> assert 1== 0, 'One does not equal zero silly!'

Traceback (innermost last):

File "<stdin>", line 1, in ?

AssertionError: One does 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 equalzero silly!

六:sys模組

另一種獲取異常資訊的途徑是通過sys 模組中exc_info()函式.此功能提供了一個3 元組的資訊。例子如下:

>>> try:

...          float('abc123')

... except:

...           import  sys

...           exc_tuple = sys.exc_info()

>>> print  exc_tuple

(<class exceptions.ValueError at f9838>,

<exceptions. ValueError instance at 122fa8>,

<traceback object at 10de18>)

>>> for eachItem in exc_tuple:

...   print eachItem

...  

exceptions.ValueError

invalid literal for float(): abc123

<traceback object at 10de18>

從sys.exc_info()得到的元組中是有:

exc_type: 異常類

exc_value: 異常類的例項

exc_traceback: 追蹤(traceback)物件