文章來源:王的機器
作者:王聖元
從上貼【錯誤類型】的內容我們知道,Python 在程序報錯時會返回詳細信息,如錯誤發生的行數和具體的錯誤類型。
首先需要明白的是,我們無法完全阻止錯誤發生,但是可以提前預防以至於程序不會崩潰。這個提前預防的動作稱為異常處理(exception handling)。
總之異常處理就是為了防患於未然。
本帖的內容如下:
try-except
try-except-else
try-except-else-finally
拋出 Exception
總結
異常處理最常見的語句就是 try-except 組合,細分又有三種類型:
知道錯誤但不確定類型,用 except Exception
知道錯誤而且確定類型,用 except some_exception
知道錯誤而且有多個錯誤
def divide(a, b): try: c = a / b print(f"Result = {c:.4f}.") except: print('Divisor is zero and division is impossible!')Divisor is zero and division is impossible!
在做除法時我們知道分母為零會報錯,因此我們把 c = a/b 這行代碼寫在 try 語句下面。測試代碼:
其實上面錯誤的具體類型我們是可以查出來的,輸入 10/0,得到該錯誤是 ZeroDivisionError。
ZeroDivisionError Traceback (most recent call last)
<ipython-input-4-e574edb36883> in <module>
----> 1 10/0
ZeroDivisionError: division by zero
這樣我們在用 except 語句處理異常時,可以在後面「顯性」寫出我們要處理的錯誤類型,即 ZeroDivisionError。
def divide(a, b): try: c = a / b print(f"Result = {c:.4f}.") except ZeroDivisionError: print('Divisor is zero and division is impossible!')Divisor is zero and division is impossible!
運行結果沒問題。
但是在實際寫代碼中,你不知道會犯什麼稀奇古怪的錯誤,如下代碼第 4 行。變量 cc 在使用之前沒有定義,報錯。
def divide(a, b): try: c = a / b d = cc + 1 print(f"Result = {c:.4f}.") except ZeroDivisionError: print('Divisor is zero and division is impossible!')
NameError Traceback (most recent call last)
<ipython-input-8-15fa568ba405> in <module>
----> 1 divide(10, 2)
<ipython-input-7-97d83e2f78ac> in divide(a, b)
2 try:
3 c = a / b
----> 4 d = cc + 1
5 print(f"Result = {c:.4f}.")
6 except ZeroDivisionError:
NameError: name 'cc' is not defined為了保險起見,我們在已經確定代碼會出現 ZeroDivisionError 的情況下,將不確定的所有錯誤都放在 except Exception 後的語句中處理,即列印出 『Something wrong!』
def divide(a, b): try: c = a / b d = cc + 1 print(f"Result = {c:.4f}.") except ZeroDivisionError: print('Divisor is zero and division is impossible!') except Exception: print('Something wrong!')Divisor is zero and division is impossible!
假設你預期代碼會出現 ZeroDivisionError 和 NameError 的錯誤,你可以用多個 except 語句來實現。
def divide(a, b): try: c = a / b d = cc + 1 print(f"Result = {c:.4f}.") except ZeroDivisionError: print('Divisor is zero and division is impossible!') except NameError: print('Some variable name is undefined!')Some variable name is undefined!
Divisor is zero and division is impossible!
運行結果沒問題。
此外,你還可以將多個 except 語句整合到第一個 except 語句中,範式如上。
兩者幾乎是等價的,下面我們換個例子來分析兩者的區別。
下面函數將變量 a 轉換成整數
如果 a 是浮點型變量 1.3 或者字符型變量 '1031',程序運行正常。
如果 a 是這種字符型變量 '1 mio',會報 ValueError 的錯誤。
如果 a 是列表型變量 [1, 2],會報 TypeError 的錯誤(這對元組、字典、集合都適用)。
def convert_to_int(a): try: int_value = int(a) print('The converted integer is', int_value) except ValueError: print("'a' is not a numerical value or expression.") except TypeError: print("The type of 'a' is not compatiable.")當程序正常運行而轉換成整型變量的輸出。
The converted integer is 1
The converted integer is 1031
當程序報錯但異常 ValueError 被處理時的輸出。
'a' is not a numerical value or expression.
當程序報錯但異常 TypeError 被處理時的輸出。
The type of 'a' is not compatiable.
我們可以將多個 except 語句寫到一個 except 語句中,兩者等價關係如下:
except exc_1:
except exc_2:
...
except exc_n:
=
except (exc_1, exc_2, ... exc_n)
關鍵點就是要用小括號把每個特定異常「打包」起來。
def convert_to_int(a): try: int_value = int(a) print('The converted integer is', int_value) except (ValueError, TypeError): print("Error occurred. Either 'a' is a numerical value " \ "or expression or type of 'a' is incompatible.")結果運行如下。
Error occurred. Either 'a' is a numerical value or expression or type of 'a' is incompatible.
Error occurred. Either 'a' is a numerical value or expression or type of 'a' is incompatible.
哪種範式更好呢?
此外我們還可以給異常起別名(alias),用以下範式,別名可以任意取,一般用 e 或者 err 當做其名稱。
except (exp_1, ... exp_n) as err
def convert_to_int(a): try: int_value = int(a) print('The converted integer is', int_value) except (ValueError, TypeError) as err: print('GOT ERROR WITH MESSAGE: {0}'.format(err.args[0]))GOT ERROR WITH MESSAGE: invalid literal for int() with base 10: '1 mio'
GOT ERROR WITH MESSAGE: int() argument must be a string, a bytes-like object or a number, not 'list'
上節講了當異常被處理時運行 except 語句中的代碼,如果沒有異常出現,我們可以加一個 else 語句,而運行其下的代碼。這時就是 try-except-else 組合。
首先要明確的是,else 語句是可有可無的。如果存在,則 else 語句應始終在 except 語句之後。
def divide(a, b): try: c = a / b except ZeroDivisionError: print('Divisor is zero and division is impossible!') else: print(f"Result = {c:.4f}.")Divisor is zero and division is impossible!
假如不管異常是否出現我們都需要運行某些代碼,我們可以加 finally 語句。這時就是 try-except-else-finally 組合。
無論是否發生異常,finally 語句始終在 try 語句運行之前執行。
在實際應用中,finally 語句在程序跑完後用於釋放資源、關閉文件或斷開資料庫連接等。
def divide(a, b): try: c = a / b except ZeroDivisionError: print('Divisor is zero and division is impossible!') else: print(f"Result = {c:.4f}.") finally: print('Error or no error, FINALLY DONE!')當異常出現的時候,沒問題,異常被處理了,而且在 finally 語句下的信息也隨後打出。
Divisor is zero and division is impossible!
Error or no error, FINALLY DONE!
當程序正常運行的時候,沒問題,結果打出,而且在 finally 語句下的信息也隨後打出。
Result = 5.0000.
Error or no error, FINALLY DONE!
當沒有實現預測的異常出現的時候,程序報錯,但是在 finally 語句下的信息還是會隨後打出。
Error or no error, FINALLY DONE!
TypeError Traceback (most recent call last)
<ipython-input-51-ae7e5f3b4399> in <module>
----> 1 divide(10, '2')
<ipython-input-48-ef792bab0247> in divide(a, b)
1 def divide(a, b):
2 try:
----> 3 c = a / b
4 except ZeroDivisionError:
5 print('Divisor is zero and division is impossible!')
TypeError: unsupported operand type(s) for /: 'int' and 'str'
這時為了處理「未預測」的異常,我們加一句 except Exception 即可。
def divide(a, b): try: c = a / b except ZeroDivisionError: print('Divisor is zero and division is impossible!') except Exception: print('Something wrong!') else: print(f"Result = {c:.4f}.") finally: print('Error or no error, FINALLY DONE!')Something wrong!
Error or no error, FINALLY DONE!
再看一個從電腦硬碟中讀取文件(假設路徑中有一個 Error.txt 的文件)的例子。
finish_task = False
try: inputFileName = input("輸入要讀取的文件名 (txt 格式): ") inputFileName = inputFileName + ".txt" inputFile = open(inputFileName, "r")except IOError: print("\n文件", inputFileName, "不能被打開")except Exception: print("\n有不明錯誤")else: print("\n正在打開文件", inputFileName, "\n") finish_task = True
for line in inputFile: print(line, end="")
print("\n\n完成讀取文件", inputFileName)finally: if finish_task: inputFile.close() print("\n關閉文件", inputFileName) else: print("\n未能完成讀取文件", inputFileName)按 Enter 下面的空白框會跳出來。
如果輸入一個錯誤的文件名,比如 asf。
輸入要讀取的文件名 (txt 格式): asf
文件 asf.txt 不能被打開
未能完成讀取文件 asf.txt
如果輸入一個正確的文件名,比如 Error。
輸入要讀取的文件名 (txt 格式): Error
正在打開文件 Error.txt
Errors or mistakes in a program are often referred to as bugs. They are almost always the fault of the programmer. The process of finding and eliminating errors is called debugging. Errors can be classified into three major groups:
I. Syntax errors
II. Runtime errors
III. Logical errors
完成讀取文件 Error.txt
關閉文件 Error.txt
除了上面處理異常的操作之外,我們還可以用 raise 關鍵詞「拋出」異常:
拋出 Python 裡內置的異常
拋出我們自定義的異常
在下例中,如果輸入非整數,我們拋出一個 ValueError(注意這是 Python 裡面內置的異常對象),順帶「This is not a positive number」的信息。
try: a = int(input("Enter a positive integer: ")) if a <= 0: raise ValueError("That is not a positive number!")except ValueError as err: print(err)在下例中,我們記錄連續兩天的組合價值
代碼如下:
def portfolio_value(last_worth, current_worth): if last_worth < 0 or current_worth < 0: raise ValueError('Negative worth!') value = current_worth - last_worth if value < 0: raise ValueError('Negative return!')兩種情況的運行結果如下:
try: portfolio_value(-10000, 10001)except ValueError as err: print(err)try: portfolio_value(10000, 9999)except ValueError as err: print(err)
但是在第二種組合增值為負的情況下,嚴格來說不算是 ValueError,頂多算個警告,這時我們可以自定義一個 NegativePortfolioValueWarning 的異常。
在 Python 裡,所有異常都是 Exception 的子類,因此在定義其類時需要
class Error(Exception):
class your_exception(Error):
具體代碼如下。
class Error(Exception): pass
class NegativePortfolioValueWarning(Error): def __init__(self, expression, message): self.expression = expression self.message = message細分 ValueError 和 NegativePortfolioValueWarning 。
def portfolio_value(last_worth, current_worth): if last_worth < 0 or current_worth < 0: raise ValueError('Negative worth!') value = current_worth - last_worth if value < 0: raise NegativePortfolioValueWarning( 'NegativePortfolioValueWarning', \ 'Negative return. Take a look!')兩種情況的運行結果如下:
try: portfolio_value(-10000, 10001)except ValueError as err: print(err)except NegativePortfolioValueWarning as err: print('[', err.args[0], '] -', err.args[1])
try: portfolio_value(10000, 9999)except ValueError as err: print(err)except NegativePortfolioValueWarning as err: print('[', err.args[0], '] -', err.args[1][ NegativePortfolioValueWarning ] - Negative return. Take a look!
一圖勝千言!
信息量太小?那這張呢?
如果你覺得文章還不錯,請大家點讚分享下。你的肯定是我最大的鼓勵和支持。
老表Pro已經滿了
所以大家加老表Max吧
每日留言
說說你最近遇到的一個編程問題?
或者新學的一個小技巧?
(字數不少於15字)