天天看點

Python回顧與整理8:錯誤和異常

0.說明

        如果想寫出使用者體驗高的代碼,那麼就需要考慮到在執行自己寫的這段代碼中在和使用者互動的過程中可能會出現的問題,也就是說,需要對可能出現的異常進行處理,隻有做好這些工作,才能寫出使用者體驗好的代碼。

1.什麼是異常

錯誤

        錯誤是文法(導緻解釋器無法解釋)或邏輯(也就是代碼品質問題)上的,在python中,當檢測到錯誤時,解釋器會指出目前流無法繼續執行下去,于是就出現了異常。

異常

        程式出現了錯誤而在正常控制流以外采取的行為。

        根據上面的解釋,可以了解為,隻要解釋器檢測到程式運作時出現了錯誤(與python解釋器不相容而導緻),就會觸發一個異常。

2.python中的異常

        如下:

異常類型

描述

簡單例子

nameerror

嘗試通路一個未聲明的變量,或者是在名稱空間中不存在的變量

1

2

3

4

<code>&gt;&gt;&gt; xpleaf</code>

<code>traceback (most recent call last):</code>

<code>  </code><code>file</code> <code>"&lt;stdin&gt;"</code><code>, line </code><code>1</code><code>, </code><code>in</code> <code>&lt;module&gt;</code>

<code>nameerror: name </code><code>'xpleaf'</code> <code>is</code> <code>not</code> <code>defined</code>

zerodivisionerror

除數為零

5

<code>&gt;&gt;&gt; </code><code>1</code><code>/</code><code>0</code>

<code>zerodivisionerror: integer division</code>

<code> </code><code>or</code> <code>modulo by zero</code>

syntaxerror

python解釋器文法錯誤

(唯一不是在運作時發生的異常,發生在編譯時,python解釋器無法把相關腳本編譯為python位元組代碼)

<code>&gt;&gt;&gt; </code><code>for</code>

<code>  </code><code>file</code> <code>"&lt;stdin&gt;"</code><code>, line </code><code>1</code>

<code>    </code><code>for</code>

<code>      </code><code>^</code>

<code>syntaxerror: invalid syntax</code>

indexerror

請求的索引走出序列範圍

<code>&gt;&gt;&gt; alist </code><code>=</code> <code>[]</code>

<code>&gt;&gt;&gt; alist[</code><code>0</code><code>]</code>

<code>indexerror: </code><code>list</code> <code>index out of </code><code>range</code>

keyerror

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

<code>&gt;&gt;&gt; adict </code><code>=</code> <code>{</code><code>'name'</code><code>: </code><code>'xpleaf'</code><code>, </code><code>'love'</code><code>: </code><code>'cl'</code><code>}</code>

<code>&gt;&gt;&gt; adict[</code><code>'clyyh'</code><code>]</code>

<code>keyerror: </code><code>'clyyh'</code>

ioerror

輸入/輸出錯誤

(任何類型的i/o錯誤都會引發ioerror異常)

<code>&gt;&gt;&gt; f </code><code>=</code> <code>open</code><code>(</code><code>'xpleaf'</code><code>)</code>

<code>ioerror: [errno </code><code>2</code><code>] no such </code><code>file</code> <code>or</code> 

<code>directory: </code><code>'xpleaf'</code>

attributeerror

嘗試通路未知的對象屬性

6

7

8

9

<code>&gt;&gt;&gt; </code><code>class</code> <code>myclass(</code><code>object</code><code>):</code>

<code>...   </code><code>pass</code>

<code>... </code>

<code>&gt;&gt;&gt; myinst </code><code>=</code> <code>myclass()</code>

<code>&gt;&gt;&gt; myinst.name</code>

<code>attributeerror: </code><code>'myclass'</code> <code>object</code> <code>has no </code>

<code>attribute </code><code>'name'</code>

3.檢測和處理異常

        需要注意的是,這和前面提到的檢測和處理錯誤并不一樣,檢測和處理錯誤的結果是會引發一個異常,這是由python解釋器完成的;當然我們也可以人為地觸發一個異常,這時開發者會認為,使用者對程式的使用是不正确的,是以才引發這樣一個異常。

        當異常出現的時候,如果不對該異常進行處理,那麼python解釋器就會中止目前程式的運作,是以,我們需要對異常進行處理,以達到即使異常出現了,也不會中止程式的執行。

(1)try-except語句

文法

<code>try</code><code>:</code>

<code>    </code><code>try_suite    </code><code>#監測這裡的異常</code>

<code>except</code> <code>exception[, reason]:</code>

<code>    </code><code>except_suit    </code><code>#異常處理代碼</code>

        reason是錯誤原因,由捕獲的異常本身帶有,隻需要定義一個變量即可以對其進行使用。

        打開一個不存在的檔案時:

<code>ioerror: [errno </code><code>2</code><code>] no such </code><code>file</code> <code>or</code> <code>directory: </code><code>'xpleaf'</code>

        其中:

<code>[errno </code><code>2</code><code>] no such </code><code>file</code> <code>or</code> <code>directory: </code><code>'xpleaf'</code>

        便是錯誤原因,可以使用try-except語句來處理上面的異常:

<code>&gt;&gt;&gt; </code><code>try</code><code>:</code>

<code>...   f </code><code>=</code> <code>open</code><code>(</code><code>'xpleaf'</code><code>, </code><code>'r'</code><code>)</code>

<code>... </code><code>except</code> <code>ioerror, e:</code>

<code>...   </code><code>print</code> <code>'could not open file:'</code><code>, e</code>

<code>could </code><code>not</code> <code>open</code> <code>file</code><code>: [errno </code><code>2</code><code>] no such </code><code>file</code> <code>or</code> <code>directory: </code><code>'xpleaf'</code>

忽略代碼,繼續執行,向上移交:

指的是,如果該層代碼(比如一個函數内)有相關的異常處理器(即except語句),就會跳到該異常處理器中進行處理,後面的代碼會被忽略(後面的其它except語句);如果在該層沒有找到對應的異常處理器,該異常會被向上移交,比如移交到調用該函數的上層代碼;當異常到達最頂層仍然沒有找到對應處理器時,就認為這個異常是未處理的,python解釋器會顯示出跟蹤記錄,然後退出。

(2)帶有多個except的try語句

<code>    </code><code>try_suite</code>

<code>except</code> <code>exception1[, reason1]:</code>

<code>    </code><code>suite_for_exception_exception1</code>

<code>except</code> <code>exception2[, reason2]:</code>

<code>    </code><code>suite_for_exception_exception2</code>

        需要注意的是,當有異常發生時,一旦找到對應的異常處理器,程式的執行流就會跳轉到該異常處理器中,其它的except語句将會被忽略。

(3)處理多個異常的except語句

<code>except</code> <code>(exception1, exception2)[, reason1]:</code>

<code>    </code><code>suite_for_exception_exception1_and_exception2</code>

        需要注意的是,這些不同的異常應該被放入到一個元組中。

拓展:包裝内建函數

如下:

<code>&gt;&gt;&gt; </code><code>def</code> <code>safe_float(obj):</code>

<code>...   </code><code>try</code><code>:</code>

<code>...     retval </code><code>=</code> <code>float</code><code>(obj)</code>

<code>...   </code><code>except</code> <code>(valueerror, typeerror):</code>

<code>...     retval </code><code>=</code> <code>'argument must be a number or numeric string'</code>

<code>...   </code><code>return</code> <code>retval</code>

執行如下:

<code>&gt;&gt;&gt; safe_float(</code><code>123</code><code>)</code>

<code>123.0</code>

<code>&gt;&gt;&gt; safe_float(</code><code>'123'</code><code>)</code>

<code>&gt;&gt;&gt; safe_float(</code><code>'foo'</code><code>)</code>

<code>'argument must be a number or numeric string'</code>

這是一種非常不錯的技巧,要善于利用。

(4)捕獲所有異常

        如果需要捕獲所有因錯誤而引起的異常,可以直接捕獲exception異常,exception是絕大多數python内建異常的基類。

        但是對于systemexit和keyboardinterupt這兩個異常,使用exception是無法捕獲的,因為它們不是exception的繼承者,原因很簡單,因為這兩個異常不是由于錯誤條件引起的。systemexit是由于目前python應用程式需要退出,keyboardinterrupt代表使用者按下了ctrl-c,想要關閉python。

        但是這三者都有一個共同的基類,那就是baseexception,也就是這三者在程式結構上是同級的,如下:

<code>baseexception</code>

<code>  </code><code>-</code><code>keyboardinterrupt</code>

<code>  </code><code>-</code><code>systemexit</code>

<code>  </code><code>-</code><code>exception</code>

<code>    </code><code>-</code><code>(</code><code>all</code> <code>other current built</code><code>-</code><code>in</code> <code>exceptions)</code>

        是以,如果真的想要捕獲所有的異常(包括非錯誤條件引起的),就可以使用baseexception,可以看下面的例子:

使用exception:無法捕獲keyboardinterrupt

        代碼如下:

<code>    </code><code>name </code><code>=</code> <code>raw_input</code><code>(</code><code>'your name:'</code><code>)</code>

<code>except</code> <code>exception:</code>

<code>    </code><code>print</code> <code>'quit'</code>

        執行如下:

<code>/usr/bin/python2</code><code>.7 </code><code>/home/xpleaf/pycharmprojects/python_book/10/test</code><code>.py</code>

<code>your name:traceback (most recent call last):</code>

<code>  </code><code>file </code><code>"/home/xpleaf/pycharmprojects/python_book/10/test.py"</code><code>, line 3, </code><code>in</code> <code>&lt;module&gt;</code>

<code>    </code><code>name = raw_input(</code><code>'your name:'</code><code>)</code>

<code>keyboardinterrupt</code>

使用baseexception:捕獲所有異常(錯誤與非錯誤條件引起的)

<code>except</code> <code>baseexception:</code>

<code>your name:quit</code>

        這樣的好處是,如果需要同時捕獲三個同級的異常,使用一個except語句就可以。

        但是需要注意的是,try-except語句是為了更好地跟蹤潛在的錯誤并在代碼裡準備好處理異常的邏輯,不應該将其作為異常過濾器來捕獲所有異常,并忽略掉這些異常。

(5)異常參數

        其實所謂異常參數,對于前面的一個例子,為什麼使用e錯誤原因時,就可以得到與該異常相關的字元串資訊呢?那是因為,異常引發後,它傳遞了一個參數給異常處理器。

        直接看下面一個例子:

10

11

12

13

14

15

16

17

18

19

<code>...     </code><code>float</code><code>(</code><code>'foo'</code><code>)</code>

<code>... </code><code>except</code> <code>valueerror, e:</code>

<code>...     </code><code>print</code> <code>'error happen:'</code><code>, e</code>

<code>error happen: could </code><code>not</code> <code>convert string to </code><code>float</code><code>: foo</code>

<code>&gt;&gt;&gt; </code>

<code>&gt;&gt;&gt; </code><code>type</code><code>(e)</code>

<code>&lt;</code><code>type</code> <code>'exceptions.valueerror'</code><code>&gt;</code>

<code>&gt;&gt;&gt; </code><code>str</code><code>(e)</code>

<code>'could not convert string to float: foo'</code>

<code>&gt;&gt;&gt; </code><code>print</code> <code>e</code>

<code>could </code><code>not</code> <code>convert string to </code><code>float</code><code>: foo</code>

<code>&gt;&gt;&gt; e.__class__</code>

<code>&gt;&gt;&gt; e.__class__.__name__</code>

<code>'valueerror'</code>

<code>&gt;&gt;&gt; e</code>

<code>valueerror(</code><code>'could not convert string to float: foo'</code><code>,)</code>

        我們可以得出下面的結論:

異常引發時,如果使用錯誤原因變量,實際上,這是一個包含來自導緻異常的診斷資訊的類執行個體,異常參數自身會組成一個元組,并存儲為這個異常類的屬性

        在這個例子中的分析是,引發了valueerror異常,然後e就是該異常的一個執行個體,并且在生成這個執行個體e的過程中,異常參數('could not convert string to float: foo',)(注意這是一個元組),就會成為e的一個屬性,而使用str(e)可以輸出診斷資訊的字元串,那是因為調用了該類執行個體的__str__()方法 。

        注意,如果用一個except語句來同時捕獲多個異常時,使用一個錯誤原因即可,因為每一個異常都會生成自己的異常參數。

        再強調:

異常參數是該異常發生時傳遞給異常處理器的一個字元串對象,它會成為這個異常類的執行個體的一個屬性,并且可以通過調用str()來獲得該診斷資訊(使用print語句,實際也是調用了該str()方法)

拓展:繼續前面的float()例子

代碼如下:

<code>def</code> <code>safe_float(</code><code>object</code><code>):</code>

<code>    </code><code>try</code><code>:</code>

<code>        </code><code>retval </code><code>=</code> <code>float</code><code>(</code><code>object</code><code>)</code>

<code>    </code><code>except</code> <code>(valueerror, typeerror), diag:</code>

<code>        </code><code>retval </code><code>=</code> <code>str</code><code>(diag)</code>

<code>    </code><code>return</code> <code>retval</code>

<code>result </code><code>=</code> <code>safe_float(</code><code>'foo'</code><code>)</code>

<code>print</code> <code>result</code>

<code>result2 </code><code>=</code> <code>safe_float([])</code>

<code>print</code> <code>result2</code>

<code>/</code><code>usr</code><code>/</code><code>bin</code><code>/</code><code>python2.</code><code>7</code> <code>/</code><code>home</code><code>/</code><code>xpleaf</code><code>/</code><code>pycharmprojects</code><code>/</code><code>python_book</code><code>/</code><code>10</code><code>/</code><code>test.py</code>

<code>float</code><code>() argument must be a string </code><code>or</code> <code>a number</code>

        ps:更進一步學習,可以考慮參考異常類的源代碼。

(6)else子句

        沒有捕獲到異常時,就執行else子句中的代碼塊,一個簡單的例子如下:

<code>...     </code><code>float</code><code>(</code><code>4</code><code>)</code>

<code>... </code><code>except</code> <code>(valueerror, typeerror), e:</code>

<code>... </code><code>else</code><code>:</code>

<code>...     </code><code>print</code> <code>'no exceptions'</code>

<code>4.0</code>

<code>no exceptions</code>

(7)finally子句

        即無論異常是否有發生或是否捕捉到異常,都會執行的語句塊。

        常用的方式如下:

try-except-finally

<code>    </code><code>a</code>

<code>except</code> <code>exception1, e:</code>

<code>    </code><code>b</code>

<code>finally</code><code>:</code>

<code>    </code><code>c</code>

try-except-else-finally

<code>else</code><code>:</code>

<code>    </code><code>d</code>

        至于書本上說的各種形式上的問題,則可以不用考慮太多,在實踐中使用時加以使用把可能出現的情況考慮到就可以了。

4.上下文管理

        try-except和try-finally的一種特定的用法是保證共享的資源的唯一配置設定,并在任務結束的時候釋放它,比如檔案、線程資源、簡單同步、資料庫連接配接等,以打開檔案為例。但其實如果用with語句,會友善很多:

<code>&gt;&gt;&gt; with </code><code>open</code><code>(</code><code>'xpleaf.txt'</code><code>, </code><code>'r'</code><code>) as f:</code>

<code>...     </code><code>for</code> <code>eachline </code><code>in</code> <code>f:</code>

<code>...         </code><code>print</code> <code>eachline</code>

<code>ioerror: [errno </code><code>2</code><code>] no such </code><code>file</code> <code>or</code> <code>directory: </code><code>'xpleaf.txt'</code>

        with語句幫我們做了很多事情:試圖打開一個檔案,如果一切正常,把檔案對象指派給f.然後用疊代器周遊檔案中的每一行,當完成時,關閉檔案,無論在這一段代碼的開始、中間還是結束時發生異常,會執行清理的代碼,此外檔案仍會被自動的關閉。

        當然這種方法僅适用于支援上下文管理協定的對象。關于上下文管理協定,由于目前還沒有使用到,是以暫不做總結。

5.字元串作為異常

        知道有這種情況就可以,在實際中仍然使用類異常。

6.觸發異常

        使用raise關鍵字就可以人為地觸發各種異常。

<code>raise</code> <code>[someexception [, args [, traceback]]]</code>

        其用法可以有如下:

raise語句的用法

raise文法

raise exclass

觸發一個異常,從cxclass生成一個執行個體(不含任何異常參數)

raise exclass()

同上,但現在不是類;通過函數調用操作符(其實就是指加上了`()`)作用于類生成一個新的exclass執行個體,同樣也沒有異常參數

raise exclass, args

同上,但同時提供的異常參數args,可以是一個參數也可以是元組

raise exclass(args)

同上

raise exclass, args, tb

同上,但提供一個跟蹤記錄(traceback)對象tb供使用

raise exclass, instance

通過執行個體觸發異常(通常是exclass的執行個體);如果執行個體是exclass的子類執行個體,那麼這個新異常的類型會是子類的類型(而不是exclass);如果執行個體既不是exclass的執行個體也不是exclass子類的執行個體,那麼會複制此執行個體為異常參數去生成一個新的exclass執行個體

raise instance

通過執行個體觸發異常:異常類型是執行個體的類型;等價于raise instance.__class__, instance(同上)

raise

重新觸發前一個異常,如果之前沒有異常,觸發typeerror

        對于raise string以及相關的方法,這裡就不提及了,因為實際上很少用到,另外對于traceback也不常用。可舉例如下:

<code>&gt;&gt;&gt; </code><code>raise</code> <code>valueerror</code>

<code>valueerror</code>

<code>&gt;&gt;&gt; </code><code>raise</code> <code>valueerror()</code>

<code>&gt;&gt;&gt; </code><code>raise</code> <code>valueerror, </code><code>'something wrong happen about value'</code>

<code>valueerror: something wrong happen about value</code>

<code>&gt;&gt;&gt; </code><code>raise</code> <code>valueerror, (</code><code>'new error'</code><code>, </code><code>'something wrong happen about value'</code><code>)</code>

<code>valueerror: (</code><code>'new error'</code><code>, </code><code>'something wrong happen about value'</code><code>)</code>

<code>&gt;&gt;&gt; </code><code>raise</code> <code>valueerror(</code><code>'something wrong happen about value'</code><code>)</code>

<code>&gt;&gt;&gt; newerror </code><code>=</code> <code>valueerror(</code><code>'something wrong happen about value'</code><code>)</code>

<code>&gt;&gt;&gt; </code><code>raise</code> <code>valueerror, newerror</code>

<code>&gt;&gt;&gt; </code><code>raise</code> <code>ioerror, newerror</code>

<code>ioerror: something wrong happen about value</code>

<code># 注意看異常類型和異常參數</code>

<code>&gt;&gt;&gt; </code><code>raise</code> <code>newerror</code>

<code>&gt;&gt;&gt; </code><code>raise</code> <code>newerror.__class__, newerror</code>

<code>&gt;&gt;&gt; </code><code>raise</code>

<code>typeerror: exceptions must be old</code><code>-</code><code>style classes </code><code>or</code> <code>derived </code><code>from</code> <code>baseexception, </code><code>not</code> <code>nonetype</code>

<code># 即達不到所描述的效果,即使前面已經有異常出現,還是會觸發typeerror異常</code>

7.斷言

        斷言通過assert語句實作,測試一個表達式,如果傳回值是假,觸發異常。觸發異常時,可以像處理普通異常一樣對它進行處理。

<code>assert</code> <code>expression[, arguments]</code>

        舉例如下:

<code>&gt;&gt;&gt; </code><code>assert</code> <code>1</code> <code>=</code><code>=</code> <code>1</code>

<code>&gt;&gt;&gt; </code><code>assert</code> <code>1</code> <code>=</code><code>=</code> <code>0</code>

<code>assertionerror</code>

<code>&gt;&gt;&gt;</code>

<code>&gt;&gt;&gt; </code><code>assert</code> <code>1</code> <code>=</code><code>=</code> <code>0</code><code>, </code><code>'one does not equal zero silly!'</code>

<code>assertionerror: one does </code><code>not</code> <code>equal zero silly!</code>

8.标準異常

        所有的标準異常都是内建的,是以可以直接在互動器或執行腳本檔案時使用,關于python目前的标準異常集,其實隻要檢視源代碼就可以很清晰地知道有哪些标準異常了,這裡就不再列出來了。

        另外,有3個直接從baseexception派生的異常子類:

systemexit

keyboardinterrupt

exception

        其它的所有内建異常都是exception的子類。

9.建立異常

        其實建立異常,隻需要繼承一個異常,并根據自己的需要進行定制即可,但由于目前還使用不到,是以先略過,實際上可以通過書上的例子和異常類的源代碼來加深對python面向對象程式設計的了解,往後再做整理。

10.(現在)為什麼用異常

        肯定是需要用異常的,因為需要達到這樣的目的:運作環境必須足夠強健,來處理應用級别的錯誤,并提供使用者級别的錯誤資訊。這樣才能提供良好的使用者體驗。

11.到底為什麼要異常

        沒有異常,将會導緻很多問題。

12.異常和sys子產品

        可以通過sys子產品中的exc_info()函數來擷取異常資訊,舉例如下:

<code>...     </code><code>float</code><code>(</code><code>'abc123'</code><code>)</code>

<code>... </code><code>except</code><code>:</code>

<code>...     </code><code>import</code> <code>sys</code>

<code>...     exc_tuple </code><code>=</code> <code>sys.exc_info()</code>

<code>&gt;&gt;&gt; </code><code>print</code> <code>exc_tuple</code>

<code>(&lt;</code><code>type</code> <code>'exceptions.valueerror'</code><code>&gt;, valueerror(</code><code>'could not convert string to float: abc123'</code><code>,), &lt;traceback </code><code>object</code> <code>at </code><code>0x7f9ba0fa0f38</code><code>&gt;)</code>

        可以看到,從sys.exc_info()中得到一個三元組,元素分别如下:

exc_type:異常類

exc_value:異常類的執行個體

exc_traceback:跟蹤記錄對象

        跟蹤記錄對象提供了發生異常的上下文,包含諸如代碼的執行幀,異常發生時的行号等資訊。

13.相關子產品

異常相關的标準庫

子產品

exceptions

内建異常(不需要導入這個子產品)

contextlib

為使用with語句的上下文對象工具

sys

主要是sys.exc_info()