Python3 錯誤和異常
- 2020 年 1 月 3 日
- 筆記
筆記內容:Python3 錯誤和異常 筆記日期:2017-11-12
Python3 錯誤和異常
- 語法錯誤
- 異常
- 異常處理
- 拋出異常
- 用戶自定義異常
- finally語句
- 預定義的清理行為
異常處理機制概述: 異常處理,是編程語言或計算機硬件里的一種機制,用於處理軟件或信息系統中出現的異常狀況(即超出程序正常執行流程的某些特殊條件)。通過異常處理,我們可以對用戶在程序中的非法輸入進行控制和提示,以防程序崩潰。 就好比一個旅遊景點,每到一個有可能出現問題情況的地方就會設置一個處理問題的處理點,不同的問題有不同的處理點,例如花粉過敏有花粉過敏的處理點,摔傷有摔傷的處理點等。程序也是如此會出現各種各樣的錯誤,同理不同的異常錯誤有不同的異常錯誤處理方法。 各種編程語言在處理異常方面具有非常顯著的不同點(錯誤檢測與異常處理區別在於:錯誤檢測是在正常的程序流中,處理不可預見問題的代碼,例如一個調用操作未能成功結束)。某些編程語言有這樣的函數:當輸入存在非法數據時不能被安全地調用,或者返回值不能與異常進行有效的區別。例如,C語言中的atoi函數(ASCII串到整數的轉換)在輸入非法時可以返回0。在這種情況下編程者需要另外進行錯誤檢測(可能通過某些輔助全局變量如C的errno),或進行輸入檢驗(如通過正則表達式),或者共同使用這兩種方法。 在python中我們可以通過try-except語句來捕捉異常,語法錯誤的話開發工具都會有提示的。
語法錯誤
語法錯誤或者稱之為解析錯誤,是初學者經常碰到的,如下實例:
>>> while True print('Hello world') File "<stdin>", line 1, in ? while True print('Hello world') ^ SyntaxError: invalid syntax
這個例子中,函數 print() 被檢查到有錯誤,是它前面缺少了一個冒號( : )。 解釋器會指出了出錯的一行,並且在最先找到的錯誤的位置標記了一個小小的箭頭。
異常
即便代碼的語法是正確的,但是在運行它的時候,也有可能發生錯誤。運行期間檢測到的錯誤被稱為異常,例如不能被0整除錯誤,或者空指針異常。 大多數的異常都不會被程序處理,都以錯誤信息的形式展現在這裡:
>>> 10 * (1/0) # 不能被0整除異常 Traceback (most recent call last): File "<stdin>", line 1, in ? ZeroDivisionError: division by zero >>> 4 + spam*3 Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: name 'spam' is not defined # 變量未聲明異常 >>> '2' + 2 # 類型異常 Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: Can't convert 'int' object to str implicitly
異常會有不同的類型,這些類型都作為信息的一部分打印出來,以上例子中的類型有 ZeroDivisionError(不能被0整除異常),NameError( 變量未聲明異常) 和 TypeError(類型異常),這些異常類型能提示開發人員發生的是什麼樣的異常,這樣就可以分析錯誤發生在何處。 錯誤信息的前面部分顯示了異常發生的上下文,並以調用棧的形式顯示具體信息。
異常處理
在python中通過try-except語句來處理異常,例如將可能會出現不能被0整除異常的代碼寫在try代碼塊里,try代碼塊里的代碼執行過程中出現異常後,就會執行except代碼塊里的代碼,代碼示例:
# 會出現異常的代碼寫在try里 try: num=10/0 print(num) # 如果上面的代碼出現異常這句代碼不會被執行 except: print("出現異常了!") # try里的代碼出現異常後就會執行這裡的代碼
運行結果:
出現異常了!
try語句按照如下方式工作;
- 首先,執行try子句(在關鍵字try和關鍵字except之間的語句)
- 如果沒有異常發生,會忽略except子句,try子句執行後就結束。
- 如果在執行try子句的過程中發生了異常,那麼try子句餘下的部分將被忽略。如果異常的類型和 except 之後的名稱相符,那麼對應的except子句將被執行。最後執行 try 語句之後的代碼。
- 如果一個異常沒有與任何的except匹配,那麼這個異常將會被拋出。
一個 try 語句可以包含多個except子句,分別來處理不同的特定的異常。但是最多只有一個分支會被執行。
以上示例的是最簡單的使用方式,能夠捕獲所有類型的異常,稱之為通用異常陷阱。如果需要捕捉特定的異常,可以在except中聲明異常的類型,那麼這個陷阱就只能捕獲你所聲明的異常類型,但是可以在末尾寫上一個通用異常陷阱,沒有被特定的陷阱所捕獲的異常最後就會被通用異常陷阱所捕獲。如果你在except中聲明了一個異常類型,可以通過as關鍵字賦值給一個變量,通過這個變量可以打印出錯誤信息,代碼示例:
try: num = 10 / 0 print(num) except ZeroDivisionError as err: # 賦值給err變量 print("出現異常:", err) except TypeError: # 聲明一個指定的異常類型 print("出現類型異常!") except: # 末尾可以使用一個通用異常 print("出現異常了!")
運行結果:
出現異常: division by zero
一個except子句可以同時處理多個異常,這些異常將被放在一個括號里成為一個元組,例如:
except (RuntimeError, TypeError, NameError):
使用raise關鍵字,可以將異常再次拋出,會拋出到解釋器中,代碼示例:
try: num = 10 / 0 print(num) except ZeroDivisionError as err: # 賦值給err變量 print("出現異常:", err) raise except TypeError: # 聲明一個指定的異常類型 print("出現類型異常!") except: # 末尾可以使用一個通用異常 print("出現異常了!")
運行結果:
出現異常: division by zero Traceback (most recent call last): File "E:/PythonProject/TestExcept.py", line 2, in <module> num = 10 / 0 ZeroDivisionError: division by zero
try except 語句還有一個可選的else子句,這個子句必須放在所有的except子句之後。這個子句將在try子句沒有發生任何異常的時候執行。例如:
try: num = 1+1 except ZeroDivisionError as err: # 賦值給err變量 print("出現異常:", err) raise except TypeError: # 聲明一個指定的異常類型 print("出現類型異常!") except: # 末尾可以使用一個通用異常 print("出現異常了!") else : print("沒有出現異常!")
運行結果:
沒有出現異常!
使用 else 子句比把所有的語句都放在 try 子句裏面要好,這樣可以避免一些意想不到的、而except又沒有捕獲的異常。
異常處理並不僅僅處理那些直接發生在try子句中的異常,而且還能處理子句中調用的函數(甚至間接調用的函數)里拋出的異常。例如:
def errorTest(): return 1/0 try: num = errorTest() except ZeroDivisionError as err: # 賦值給err變量 print("出現異常:", err) except TypeError: # 聲明一個指定的異常類型 print("出現類型異常!") except: # 末尾可以使用一個通用異常 print("出現異常了!") else : print("沒有出現異常!")
運行結果:
出現異常: division by zero
拋出異常
上面示例也用到了raise 關鍵字,通過這個關鍵字可以拋出異常到外部。也可以使用此關鍵字在代碼中拋出特定的異常,如果這個關鍵字寫在except里,並且沒有指定要拋出的異常,那麼這個raise 就會拋出這個陷阱里的異常,代碼示例:
try: num=10/0 except ZeroDivisionError: raise
運行結果:
Traceback (most recent call last): File "E:/PythonProject/TestExcept.py", line 3, in <module> num=10/0 ZeroDivisionError: division by zero
使用raise 關鍵字拋出指定的異常示例:
i=0 j=1 if i!=0: k=j/i else: print("拋出一個異常:") raise ZeroDivisionError
運行結果:
Traceback (most recent call last): File "E:/PythonProject/TestExcept.py", line 8, in <module> raise ZeroDivisionError ZeroDivisionError
拋出的異常可以指定一個字符串類型的參數,這個參數也會隨着異常信息打印出來,代碼示例:
i=0 j=1 if i!=0: k=j/i else: print("拋出一個異常:") raise ZeroDivisionError("我是異常")
運行結果:
拋出一個異常: Traceback (most recent call last): File "E:/PythonProject/TestExcept.py", line 9, in <module> raise ZeroDivisionError("我是異常") ZeroDivisionError: 我是異常
用戶自定義異常
你可以通過創建一個新的exception類來擁有自己的異常。異常應該繼承自 Exception 類,或者直接繼承,或者間接繼承,例如:
# 這是一個自定義的異常類 >>> class MyError(Exception): # 繼承於Exception類 def __init__(self, value): # 這是初始化方法,也就是構造器 self.value = value #這是這個類的屬性 def __str__(self): # 類中的每個方法都需要有一個self參數,通過這個參數來獲取類屬性的值 return repr(self.value) >>> try: raise MyError(2*2) #拋出自定義的異常類 except MyError as e: print('My exception occurred, value:', e.value) #會被這裡捕獲 My exception occurred, value: 4 # 運行結果 >>> raise MyError('oops!') # 拋出自定義的異常類 # 打印的異常信息 Traceback (most recent call last): File "<stdin>", line 1, in ? __main__.MyError: 'oops!'
在這個例子中,類 Exception 默認的 _init_( ) 被覆蓋。 當創建一個模塊有可能拋出多種不同的異常時,一種通常的做法是為這個包建立一個基礎異常類,然後基於這個基礎類為不同的錯誤情況創建不同的子類:
class Error(Exception): """Base class for exceptions in this module.""" pass class InputError(Error): """Exception raised for errors in the input. Attributes: expression -- input expression in which the error occurred message -- explanation of the error """ def __init__(self, expression, message): self.expression = expression self.message = message class TransitionError(Error): """Raised when an operation attempts a state transition that's not allowed. Attributes: previous -- state at beginning of transition next -- attempted new state message -- explanation of why the specific transition is not allowed """ def __init__(self, previous, next, message): self.previous = previous self.next = next self.message = message
大多數的異常的名字都以」Error」結尾,就跟標準的異常命名一樣。
finally語句
try 語句還有另外一個可選的子句,這個語句無論在任何情況下都會執行,也就是所謂的最終執行塊,這個代碼塊里的代碼不管什麼有沒有發生異常都會被執行,一般用於執行close之類的關閉資源的語句。 例如:
try: filedata = open("E:/test.txt") except IOError: print("文件打開失敗!") finally: filedata.close() print("資源已關閉!")
運行結果:
資源已關閉!
以上例子不管 try 子句裏面有沒有發生異常,finally 子句都會執行。 如果一個異常在 try 子句里(或者在 except 和 else 子句里)被拋出,而又沒有任何的 except 把它截住,那麼這個異常會在 finally 子句執行後再次被拋出。 代碼示例:
try: filedata = open("E:/test.txt") raise ZeroDivisionError except IOError: print("文件打開失敗!") finally: filedata.close() print("資源已關閉!")
運行結果:
資源已關閉! Traceback (most recent call last): File "E:/PythonProject/TestExcept.py", line 3, in <module> raise ZeroDivisionError ZeroDivisionError
預定義的清理行為
一些對象定義了標準的清理行為,無論系統是否成功的使用了它,一旦不需要它了,那麼這個標準的清理行為就會執行。 這面這個例子展示了嘗試打開一個文件,然後把內容打印到屏幕上:
for line in open("myfile.txt"): print(line, end="")
以上這段代碼的問題是,當執行完畢後,文件會保持打開狀態,並沒有被關閉。 之前介紹過的關鍵詞 with 語句就可以保證諸如文件之類的對象在使用完之後一定會正確的執行他的清理方法,這種就是預定義的清理行為:
with open("myfile.txt") as f: for line in f: print(line, end="")
以上這段代碼執行完畢後,就算在處理過程中出問題了,文件 f 也會關閉。