一、錯誤和異常
1、錯誤
從軟件方面來講,錯誤通常是語法或邏輯上的。語法錯誤會導致程序代碼不能被解釋器解釋,這些錯誤必須在程序執行前糾正。當程序的語法正確后,剩下的就是邏輯錯誤了。邏輯錯誤可能是由于不完整或是不合法的代碼邏輯所致,還可能是由于代碼邏輯無法生成或執行。
編譯時會檢查語法錯誤,編譯完成后 Python 解釋器會在程序運行時檢測邏輯錯誤。當檢測到一個錯誤,Python 解釋器就引發一個異常,并顯示異常的詳細信息。程序員可以根據這些信息迅速定位問題并進行調試。
2、異常
異常處理通常分為兩個階段:首先是解釋器在執行代碼過程中發生錯誤并引起異常,異常會在錯誤檢測到的地方引發,并將當前流打斷,然后是檢測異常并執行相關的處理程序。
異常引發后,可以調用很多不同的操作。可以是忽略錯誤(記錄錯誤但不采取任何措施),或是減輕問題的影響后設法繼續執行程序。所有的這些操作都代表一種程序的繼續,關鍵是程序員在錯誤發生時可以指示程序如何執行。
類似 Python 這樣支持引發和處理異常(這更重要)的語言,可以讓開發人員可以在錯誤發生時更直接地控制它們。程序員不僅僅有了檢測錯誤的能力,還可以在它們發生時采取更可靠的補救措施。由于有了運行時管理錯誤的能力,應用程序的健壯性有了很大的提高。
常見的異常情況
NameError 表示我們訪問了一個沒有初始化的變量。
任何數值被零除都會導致一個 ZeroDivisionError 異常。
SyntaxError 異常是語法錯誤,是唯一不是在運行時發生的異常。它代表 Python 代碼中有一個不正確的語法結構,在它改正之前程序無法執行。這些錯誤一般都是在編譯時發生,Python 解釋器無法把你的腳本轉化為 Python 字節代碼,當然這也可能是你導入一個有缺陷的模塊的時候。
IndexError 異常在你嘗試使用一個超出范圍的值來索引序列時引發。
映射對象,例如字典,是依靠關鍵字(keys)訪問數據值的。如果使用錯誤的或是不存在的鍵請求字典就會引發一個 KeyError 異常。
類似嘗試打開一個不存在的磁盤文件一類的操作會引發一個 FileNotFoundError 異常。
屬性和方法被定義后,我們可以使用熟悉的點操作符訪問它,但如果是沒有定義的屬性,將導致一個 AttributeError 異常。
二、try 語句
要給你的代碼添加錯誤檢測及異常處理,只要將它們封裝在 try-except 語句當中。 try 子句的代碼塊,就是你打算監測的代碼,檢查有無異常發生。except 句子的代碼塊,則是你處理異常的代碼。
在程序運行時,解釋器嘗試執行 try 子句塊里的所有代碼,如果代碼塊完成后沒有異常發生,就會忽略 except 子句繼續執行。當 try 子句塊中發生異常時,Python 解釋器會依次檢查 except 子句,直到找到與異常匹配的子句,并執行其中的代碼塊。
當找到一個匹配的 except 子句時,異常就被賦給 except 子句中 as 關鍵字后面的目標,然后執行 excep 子句的 suite。所有 except 子句必須有一個可執行塊。
try 語句塊中異常發生點后的剩余語句永遠不會執行。 一旦一個異常被引發,就必須決定控制流下一步到達的位置。try子句代碼塊中的剩余代碼將被忽略,解釋器將搜索處理器,一旦找到,就開始執行處理器中的代碼。
如果沒有找到合適的處理器,那么異常就向上移交給調用者去處理。如果在上層調用者也沒找到對應處理器,該異常會繼續被向上移交,直到找到合適處理器。如果到達最頂層仍然沒有找到對應處理器,那么就認為這個異常是未處理的,Python 解釋器會顯示出跟蹤返回消息,然后退出。
如果同一異常存在兩個嵌套處理程序,并且內部處理程序的 try 子句中發生異常,則外部處理程序將不處理異常。如果 except 子句頭部的一個表達式引發了異常, 那么就會中斷原異常處理器的搜索, 而在外層代碼和調用棧上搜索新的異常處理器(就好像是整個 try 語句發生了異常一樣)。
1、try-except 語句
我們使用 try-except 語句進行異常的監控,并且對監測到的異常進行處理。最常見的 try-except 語句語法如下所示。它由 try 子句和 except 子句組成,也可以有一個可選的 as 語句來保存錯誤原因。
try:
try_suite # 監控這里的異常
except Exception[as reason]:
except_suite # 異常處理代碼
2、處理多個異常的 except 語句
我們可以在一個 except 子句里指定并處理多個異常。except 語句在指定多個異常時要求異常被放在一個元組里:
try:
try_suite
except (Exception1, Exception2)[as reason]:
suite_for_Exception1_and_Exception2
3、帶有多個 except 的 try 語句
你可以把多個 except 語句連接在一起,處理一個 try 塊中可能發生的多種異常,我們可以分別為每個異常類型分別創建對應處理程序,這樣就可以更加細致的處理錯誤并得到更詳細的錯誤信息。
try:
try_suite
except Exception1[as reason1]:
suite_for_exception_Exception1
except Exception2[as reason2]:
suite_for_exception_Exception2
同樣,首先嘗試執行 try 子句,如果沒有錯誤,忽略所有的 except 從句繼續執行。如果發生異常,解釋器將在這一串處理器(except 子句)中從上到下查找匹配的異常。如果找到對應的處理器,執行流將跳轉到這里。
4、捕獲所有異常
如果查詢異常類繼承的樹結構,我們會發現 Exception 類是在最頂層的,所以我們可以在 except 子句中通過指定 Exception 異常來捕獲所有的異常:
try:
pass
except Exception [as e]:
# error occurred, log 'e', etc.
另一個我們不太推薦的方法是使用 裸 except 子句來匹配所有的異常:
try:
pass
except:
# error occurred, etc.
雖然這樣的代碼可以捕獲大多異常,但它不是好的 Python 編程樣式。它會捕獲所有異常,你可能會忽略掉重要的錯誤,正常情況下這些錯誤應該讓調用者知道并做一定處理。通常我們只是在多個 except 組合子句的最后的 except 子句中來捕捉 Exception 異常。
Python 提供給程序員的 try-except 語句是為了更好地跟蹤潛在的錯誤并在代碼里準備好處理異常的邏輯。它的目的是減少程序出錯的次數并在出錯后仍能保證程序正常執行。
5、異常參數
異常也可以有參數,當異常被引發后參數是作為附加幫助信息傳遞給異常處理器的。標準內建異常提供至少一個參數,指示異常原因的一個字符串。
異常的參數可以在處理器里忽略,但 Python 中可以在 except 關鍵字的后面使用過 as 關鍵字來保存這個值。我們已經在上邊接觸到相關內容:要想訪問提供的異常原因,你必須保留一個變量來保存這個參數。把這個參數放在 as 語句的后面,并接在 except 語句要處理的異常后面。except 語句的這個語法可以被擴展為:
# single exception
except Exception[as reason]:
suite_for_Exception_with_Argument
# multiple exceptions
except (Exception1, Exception2, ..., ExceptionN)[as reason]:
suite_for_Exception1_to_ExceptionN_with_Argument
reason 將會是一個包含來自導致異常的代碼的診斷信息的類實例。異常參數自身會組成一個元組,并存儲為類實例(異常類的實例)的屬性。上邊的第一種用法中,reason 將會是一個 Exception 類的實例。
對于大多內建異常,也就是從 StandardError 派生的異常,這個元組只包含一個指示錯誤原因的字符串。一般說來,異常的名字已經是一個滿意的線索了,但這個錯誤字符串會提供更多的信息。操作系統或其他環境類型的錯誤,例如 IOError ,元組中會把操作系統的錯誤編號放在錯誤字符串前。
6、else 子句
我們已經看過 else 語句段配合其他的 Python 語句,比如條件和循環。至于 try-except 語句,它的功能和你所見過的其他 else 語句沒有太多的不同:在 try 語句范圍中沒有異常被檢測到時,執行 else 子句。
在 else 子句范圍中的任何代碼運行前,try 范圍中的所有代碼必須完全執行成功(也就是,結束前沒有引發異常)。
try:
pass
except Exception[as reason]:
pass
else:
pass
7、finally 子句
finally 子句是無論異常是否發生、是否捕捉都會執行的一段代碼。你可以將 finally 子句僅僅配合 try 子句一起使用,也可以和 try-except(else 也是可選的,但是必須和 except 子句配合使用)一起使用。
try:
pass
except Exception[as reason]:
pass
else:
pass
finally:
pass
另一種使用 finally 的方式是 finally 單獨和 try 連用。
try:
pass
finally:
pass
當在 try 子句范圍中產生一個異常時,會立即跳轉到 finally 語句段。當 finally 中的所有代碼都執行完畢后,會繼續向上一層引發異常。
三、觸發異常
到目前為止,我們所見到的異常都是由解釋器引發的,由于執行期間的錯誤而引發。程序員在編寫 API 時可能希望手動觸發異常,為此 Python 提供了一種機制可以讓程序員明確的觸發異常,就是 raise 語句。
1、raise 語句
rasie 一般的用法是:
raise [SomeException [, args [, traceback]]]
第一個參數 SomeExcpetion 是觸發異常的名字。如果有,它必須是一個異常的類或實例,如果有其他參數(args 或 traceback),則必須提供 SomeExcpetion。
第二個符號為可選的 args(比如參數,值)來傳給異常。這可以是一個單獨的對象也可以是一個對象的元組。如果 args 原本就是元組,那么就將其傳給異常去處理;如果 args 是一個單獨的對象,生成只有一個元素的元組(就是單元素元組)。大多數情況下,使用單一的字符串用來指示錯誤的原因。如果傳的是元組,通常的組成是一個錯誤字符串,一個錯誤編號,可能還有一個錯誤的地址,比如文件,等等。
最后一項參數,traceback,同樣是可選的(實際上很少用它),如果有的話,則是當異常觸發時新生成的一個用于異常-正常化(exception—normally)的追蹤(traceback)對象。當你想重新引發異常時,第三個參數很有用(可以用來區分先前和當前的位置)。如果沒有這個參數,就填寫 None。
最常見的用法為 SomeException 是一個類。不需要其他的參數,但如果有的話,可以是一個單一對象參數,一個參數的元組,或一個異常類的實例。如果參數是一個實例,可以由給出的類及其派生類實例化(已存在異常類的子集)。若參數為實例,則不能有更多的其他參數。
2、斷言
斷言是一句必須等價于布爾真的判定;此外,發生異常也意味著表達式為假。斷言可以簡簡單單的想象為raise-if-not 語句。測試一個表達式,如果返回值是假,觸發異常。如果斷言成功不采取任何措施(類似語句),否則觸發 AssertionError (斷言錯誤)的異常。
assert expression[, arguments]
AssertionError 異常和其他的異常一樣可以用 try-except 語句塊捕捉,但是如果沒有捕捉,它將終止程序運行而且提供一個如上的 traceback。
下面是我們如何用 try-except 語句捕獲 AssertionError 異常:《Python基礎手冊》系列:
Python基礎手冊 1 —— Python語言介紹
Python基礎手冊 2 —— Python 環境搭建(Linux)
Python基礎手冊 3 —— Python解釋器
Python基礎手冊 4 —— 文本結構
Python基礎手冊 5 —— 標識符和關鍵字
Python基礎手冊 6 —— 操作符
Python基礎手冊 7 —— 內建函數
Python基礎手冊 8 —— Python對象
Python基礎手冊 9 —— 數字類型
Python基礎手冊10 —— 序列(字符串)
Python基礎手冊11 —— 序列(元組&列表)
Python基礎手冊12 —— 序列(類型操作)
Python基礎手冊13 —— 映射(字典)
Python基礎手冊14 —— 集合
Python基礎手冊15 —— 解析
Python基礎手冊16 —— 文件
Python基礎手冊17 —— 簡單語句
Python基礎手冊18 —— 復合語句(流程控制語句)
Python基礎手冊19 —— 迭代器
Python基礎手冊20 —— 生成器
Python基礎手冊21 —— 函數的定義
Python基礎手冊22 —— 函數的參數
Python基礎手冊23 —— 函數的調用
Python基礎手冊24 —— 函數中變量的作用域
Python基礎手冊25 —— 裝飾器
Python基礎手冊26 —— 錯誤 & 異常
Python基礎手冊27 —— 模塊
Python基礎手冊28 —— 模塊的高級概念
Python基礎手冊29 —— 包