Python異常處理與程序調試

在程序執行過程中,遇到出錯情況在所難免。有些錯誤可以預料,可在程序處理中考慮進去;有些錯誤是意料之外的,例如,若在讀取文件其間,計算機上的其他程序已將其刪除,如何處理?若程序從網站下載網頁時,該網站突然崩潰,如何處理?

Python採用的措施是引發異常。異常是一種特殊的錯誤對象,程序可以捕獲並檢查它們,以決定如何處理錯誤。 異常可能改變程序的控制流程。根據發生的時機,異常可能導致執行流程跳出函數或進入處理錯誤的代碼塊。

通常,我們無法確定哪一行可能引發異常,Python提供了一個特殊的異常處理結構,可用於捕獲異常,並確保無論是否出現異常都將執行清理代碼。

常見的異常類型:
SyntaxError:Python 不能理解程序
NameError:局部或全局變數名找不到
AttributeError:屬性引用失敗
IndexError:索引引用越界
TypeError:操作數的類型不正確
ValueError:操作數類型正確,但值非法
ZeroDivisionError:被零除
FileNotFoundError:文件未找到
IOError:IO system 報告故障

例如:1. 試圖存取列表上界之外的元素將引發IndexError
如:Test = [1,2,3]
Test[4]
2. 試圖轉換不適當的類型將引發TypeError
如:int(Test)
3. 引用不存在的變數將引發NameError
如:a4. 不同的數據類型參加運算而沒有強制類型轉換將引發TypeError
如:『a』/4


如何處理異常?

  • 什麼都不做:替換成預設值,使程序繼續執行
    • Bad idea!用戶可能會懷疑結果
  • 返回一個「error」值
    • 選擇一個什麼樣的錯誤值?
    • 主調程序必須包含檢查這種特殊值和處理一系列錯誤的代碼
  • 停止執行,發出錯誤條件信號
    • 在Python中即引發異常,捕獲異常並處理之

例:傳統處理程序出錯及Python處理異常比較

  • 編寫函數getRatios(v1, v2)。
  • 假定參數v1、v2是等長的數字列表,要求返回一個列表,該列表包含v1[i]/v2[i]有意義的值。

1. 使用傳統程序設計方法處理錯誤:

調用及執行:

2. 使用Python異常處理機制實現:

執行:

對比之下,傳統處理錯誤方法的缺點顯而易見:

  • 程序難讀,因此難以維護和修改
  • 效率較低

一、異常的處理

1、try…except的使用

  • try…except語句用於處理問題語句,捕獲可能出現的異常。
  • try子句中的代碼塊放置可能出現異常的語句,except子句中的代碼塊處理異常。當異常出現時,Python會自動生成1個異常對象,該對象包括異常的具體信息,以及異常的種類和錯誤位置。

例如:試圖打開不存在的文件

說明:出現了FileNotFoundError異常


例:使用try…except捕獲FileNotFoundError異常

執行結果:


又如:Python內置函數和庫函數通常在出現意外情況時引發異常

同樣可以使用try…except語句來處理該異常。try…except語句後還可以添加1個else子句,當try子句中的代碼發生異常時,程序直接跳轉到except子句;反之,程序將執行else子句。


例:捕獲並處理除數為0的ZeroDivisionError異常

例:異常處理的嵌套

執行結果:

2、try…finally的使用

  • try…except語句還可以添加1個finally子句,無論異常是否發生,finally子句都會被執行。
  • finally子句通常用於關閉因異常而不能釋放的系統資源。

例:使用try…finally處理異常

執行結果:

3、使用raise拋出異常

  • 當程序中出現錯誤時,Python會自動引發異常。另外,在程序的任何地方都可以使用raise語句故意引發異常。
  • 一旦執行了raise語句,raise語句後的代碼將不被執行。
  • raise語句通常用於拋出自定義異常,因為自定義異常並不在Python的控制範圍之內,不會被Python自動拋出,應使用raise語句手工拋出。

例:使用raise拋出異常

執行結果:

4、自定義異常

  • Python允許自定義異常,用於描述Python異常體系中沒有涉及的異常情況。
  • 自定義異常必須繼承Exception類。
  • 自定義異常按照命名規範以Error結尾,顯式地表示該類是異常類。
  • 自定義異常使用raise語句引發,其只能通過手工方式觸發。

例:自定義異常

執行結果:

5、assert語句的使用

  • assert語句用於檢測某個條件表達式是否為真。
  • assert語句又稱為斷言語句,即assert認為檢測的表達式永遠為真。
  • if語句中的條件判斷都可以使用assert語句檢測。例如,檢測某個元組中元素的個數是否大於1,如果assert語言斷言失敗,會引發AssertionError異常。

例:assert語句使用

在何處使用斷言?

  • 使用斷言的目的是為了儘早識別bug且清楚它們是在何處出現的
    • 在第一次碰到問題時就捕獲它,使調試更容易,而不是之後再追蹤
  • 不要將斷言用於測試之處,但可以作為測試的補充
  • 若用戶提供了錯誤輸入時應儘可能依靠拋出異常去處理,而斷言常用於檢查參數或值的類型。

6、多種異常的處理

可在except子句中指定多種異常來處理多種異常; 如果要分別處理不同的異常,可使用多個except子句; 如果在except子句中沒有指定異常,它將捕獲所有異常。


例:處理多種異常

執行結果:

二、調試

  • 如果總能寫出正確的代碼,並在第一次第一次測試時就能正確執行,當然很好。但實際編程過程中並不總是如此順利。
  • 如何測試代碼是否正確,常用的方法有:
    • 黑盒測試—通過特定規範執行路徑
    • 白盒測試—通過代碼執行路徑
    • 將調試作為一個搜索過程—使用二分查找的方法,分離並檢查錯誤來源

1、測試和調試

  • 在調試程序過程中我們需要一些方法:
    • 測試方法—使用不同的例子運行代碼看其是否正確的方法
    • 調試方法—已經發現程序中有問題,如何修正程序的方法

何時進行測試?何時進行調試?

  • 實際上,如果我們能弄清楚如何設計代碼,那麼測試和調試會更加簡單。優秀的程序員會有這樣的經驗:
    • 將代碼分解成獨立的模塊,從而獨立地測試和調試;
    • 寫出好的文檔(寫明輸入、輸出的期望是什麼;即使代碼沒有執行對測試的限制,這種文檔也是有價值的);
    • 記錄下可能的各種假設;

測試之前要完成的工作:

  • 確保代碼可以運行
    • 剔除語法錯誤
    • 剔除靜態語義錯誤
    • 事實上,Python解釋器可以自動處理上述兩種錯誤
  • 準備一套預期結果(即對於一個特定的輸入,期待會有怎樣的輸出)

2、測試套件

  • 我們希望找到一系列輸入,它們很有可能暴露錯誤,且不會花太多時間,但卻十分有效,這就是所謂測試套件。
    • 將輸入分解為子集,為代碼正確性提供等效信息;
    • 構造測試套件,其中至少包含每個子集的一個元素;
    • 運行測試套件。

例:測試套件應用

輸入空間是所有的整數對 可能的子集:

  • x為正,y為正
  • x為負,y為負
  • x為正,y為負
  • x為負,y為正
  • x=0,y=0
  • x=0,y!=0
  • x!=0,y=0

輸入空間的劃分原則:
- 上例中當然也可以有其他的輸入選擇,例如x是質數,y不是;y是質數,x不是;x和y都是質數;x和y都不是質數。但這與問題不相關。- 實際上輸入空間經常具有自然邊界:如:整數有正、負、零值;
根據這個觀點可以將測試數據劃分為9個子集(見上):
將x=0,y!=0劃分為x=0,y為正數和x=0,y為負數
x!=0,y=0也同樣劃分

測試方法:
- 隨機測試—代碼的正確率隨測試次數的增加而增加;- 黑盒測試:在測試時,把程序看作一個不能打開的黑盒子,在完全不考慮程序內部結構和內部特性的情況下,測試者在程序介面進行測試,它只檢查程序功能是否按照需求規格說明書的規定正常使用,程序是否能適當地接收和正確的輸出- 白盒測試:是通過程序的源代碼進行測試而不使用用戶界面。這種類型的測試需要從代碼句法發現內部代碼在演算法,溢出,路徑,條件等等中的缺點或者錯誤,進而加以修正。

3、黑盒測試

  • 測試套件的設計使人們無需查看代碼,其優點為:
    • 編程者以外的其他人也可以使用其進行測試;
    • 可以避免編程者潛在的偏見,使得發現錯誤更為容易;
    • 同一個測試套件可以被重複利用,即使改變了程序代碼。

4、白盒測試

  • 使用代碼本身引導測試用例的設計;
  • 一個好的白盒測試套件,也被稱為窮舉路徑測試,代碼片段的每一個路徑至少都被檢測一次;
  • 當然,即使是一個窮舉路徑測試也可能遺漏錯誤,這取決於例子的選擇。

例:求某個數的絕對值(使用白盒測試)

選擇測試套件{-2,2},這是窮舉路徑; 但是遺漏了檢測abs(-1); 測試的邊界應該選擇{-2,-1,2} 修改程序(if x<=-1)

白盒測試的經驗法則:
1. 執行所有if語句的兩條分支;
2. 確保每一個except語句被執行;
3. 對於每一個for循環,需要測試以下情況:
- 循環一次都未被執行; - 循環體只被執行一次; - 循環體被執行一次以上4. 對於每一個while循環,需要測試以下情況:
- 與for循環測試相同 - 還要測試所有退出循環的情況5. 對於遞歸,要測試沒有遞歸、有一次遞歸和多次遞歸的情況。

5、將二分查找思路用於調試


例:用二分查找思路調試判迴文程序。

執行結果:








三、使用自帶IDEL調試程序

  • IDLE是Python自帶的一個簡易的IDE,具有程序調試的功能,並且提供交互環境和腳本文件調試兩種模式。
  • 打開IDLE後默認是一個交互環境,可以直接編寫Python代碼並且能試試查看輸出。
  • 單擊[Debug]|[Debugger]會彈出Debug Control調試界面。 界面上有5個按鈕:Go(運行)、Step(單步調試)、Over(跳過)、Out(跳出)和Quit(退出)。 還有4個可選項:Stack(堆棧)、Source(源碼)、Locals(局部變數)和Globals(全局變數)。

例:編寫兩個數的除法函數並調試(在交互環境下調試)

腳本文件調試:


python學習課程目錄

推薦閱讀:

TAG:Python入門 | Python開發 | Python |