不間斷 Python 挑戰 Day 33 - 異常處理

2022/06/01閱讀時間約 15 分鐘
不管你是程式的新手或老手,不管你是不是第一天寫Python程式,在編譯或執行時絕對有出現程式錯誤或異常的時候,也就是俗稱的「bug」,這對於程式編譯人員是好事,你可以在程式撰寫階段就事先排除這些異常,或是加上捕捉異常的程式區段以及處理程序,讓程式即使出現異常也可以繼續執行。因此,首先我們可以先來看一下常見的異常事件有哪些,相信寫過Python程式的人,很多都已經遇過了。

常見異常事件

AttributionError:物件沒有這個屬性
Exception:通用型的錯誤事件
FileNotFoundError:找不到open()開啟的檔案
FloatingPointError:浮點計算錯誤
ImportError:匯入模組或物件失敗
IndentationError:縮排錯誤
IndexError:索引超出範圍
IOError:輸入/輸出操作失敗
KeyError:不存在這個鍵
MemoryError:記憶體溢位
NameError:物件未宣告
OverflowError:數值運算超出最大限制
SyntaxError:語法錯誤
SystemError:直譯器的系統錯誤
TypeError:資料型別錯誤
ValueError:傳入無效的參數
ZeroDivisionError:除數為0
以下我們來舉一些常見的異常事件,看一下Python的Traceback會告訴我們什麼訊息。

FileNotFoundError

嘗試開啟一個程式路徑底下找不到的檔案會出現FileNotFoundError。
# FileNotFoundError
with open("file.txt", "r") as file:
  file.read()
這時Python的Traceback會列出異常報告,告訴我們錯誤出現在哪個檔案的第幾行,以及錯誤的類型和說明。如以下,報告中明確指出了Python直譯器發現在「main.py」的「with open("file.txt", "r") as file:」這一行出現了錯誤,錯誤的類型是「FileNotFoundError」,原因是「No such file or directory: 'file.txt'」,並沒有存在「file.txt」這個檔案讓程式開啟。
Traceback (most recent call last):
File "C:\Users\wjweng\PycharmProjects\marathon_python\Day33\main.py", line 2, in
with open("file.txt", "r") as file:
FileNotFoundError: [Errno 2] No such file or directory: 'file.txt'

KeyError

嘗試透過字典中不存在的鍵來取得值會出現KeyError。
# KeyError
dictionary = {"台北市": 100}
value = dictionary["新北市"]
dictionary這個字典僅存在"台北市": 100這樣一組鍵值對,當我們試圖使用不存在的"新北市"做為鍵來取值便會出現錯誤,Traceback訊息如下:
Traceback (most recent call last):
  File "C:\Users\wjweng\PycharmProjects\marathon_python\Day33\main.py", line 2, in 
    value = dictionary["新北市"]
KeyError: '新北市'

IndexError

使用超出串列範圍的索引值來存取串列內容會出現IndexError。
# IndexError
city_list = ["台北市", "新北市", "台中市"]
city = city_list[3]
串列city_list存在三個元素,索引值範圍為0~2,當使用3做為索引來取用串列值會出現錯誤,Traceback訊息如下:
Traceback (most recent call last):
  File "C:\Users\wjweng\PycharmProjects\marathon_python\Day33\main.py", line 2, in 
    city = city_list[3]
IndexError: list index out of range

TypeError

使用非預期的資料型別來做操作會出現TypeError。
# TypeError
text = "大家好"
print(text + 1)
上述程式使用字串型別的變數text來做加法運算,因兩者資料型別不一致而無法執行,Traceback訊息如下:
Traceback (most recent call last):
  File "C:\Users\wjweng\PycharmProjects\marathon_python\Day33\main.py", line 2, in 
    print(text + 1)
TypeError: can only concatenate str (not "int") to str

異常處理程序

try - except

try - except語句為最基本的異常處理程序,try語句區塊中為可能需要捕捉異常情況的指令,except語句區塊為當異常情況發生時的處理方式,語法如下:
try:
  可能需要捕捉異常情況的指令
except:
  異常情況發生時的處理方式
如以下範例,我們將前述IndexError的範例程式放入try語塊,並在except中捕捉IndexError,當出現這項錯誤時,會執行except語塊中的內容,避免程式因錯誤而終止。
def print_city(index):
  city_list = ["台北市", "新北市", "台中市"]

  try:
    city = city_list[index]
  except IndexError:
    print("索引值超出範圍!")

print_city(3)
執行結果:
索引值超出範圍!

try - except - else

此語法在try - except語句下又擴充了else指令,當try語塊中的指令執行正常時,便會執行else語塊中的內容,擴充後的語法如下:
try:
  可能需要捕捉異常情況的指令
except:
  異常情況發生時的處理方式
else:
  指令正確執行時的處理方式
以下程式中,0可以做為索引值來取得city_list的元素,因此程式會跳過except區塊,直接執行else區塊的內容。
def print_city(index):
  city_list = ["台北市", "新北市", "台中市"]

  try:
    city = city_list[index]
  except IndexError:
    print("索引值超出範圍")
  else:
    print(city)

print_city(0)
執行結果:
台北市

try - except - else - finally

finally指令放在異常處理程序的最後方,區塊內放置無論是否有異常情況發生都會執行的內容,完整的語法如下:
try:
  可能需要捕捉異常情況的指令
except:
  異常情況發生時的處理方式
else:
  指令正確執行時的處理方式
finally:
  程式正常或異常皆會執行的指令
以下範例除了增加了finally區塊外,其餘同上述程式,執行後會多印出finally區塊的內容。
def print_city(index):
  city_list = ["台北市", "新北市", "台中市"]

  try:
    city = city_list[index]
  except IndexError:
    print("索引值超出範圍")
  else:
    print(city)
  finally:
    print("執行完畢!")

print_city(0)
執行結果:
台北市
執行完畢!

捕捉多個異常

若預期的錯誤可能不只一種,可以設計多個except區塊捕捉不同的異常,或是在一個except區塊內來捕捉多個異常。
  • 使用多個except區塊捕捉不同的異常,語法如下:
try:
  可能需要捕捉異常情況的指令
except 異常事件1:
  異常事件1發生時的處理方式
except 異常事件2:
  異常事件2發生時的處理方式
......
延續之前的範例,除了原有的IndexError以外,新增捕捉TypeError,當print_city()輸入的參數為3及"a"時,分別跳出不同的錯誤訊息。
def print_city(index):
  city_list = ["台北市", "新北市", "台中市"]

  try:
    city = city_list[index]
  except IndexError:
    print("索引值超出範圍!")
  except TypeError:
    print("型別錯誤!")
  else:
    print(city)
  finally:
    print("執行完畢!")

print_city(3)
print_city("a")
執行結果:
索引值超出範圍!
執行完畢!
型別錯誤!
執行完畢!
  • 使用一個except區塊捕捉多個異常,語法如下:
try:
  可能需要捕捉異常情況的指令
except (異常事件1, 異常事件2, ......):
  異常事件發生時的處理方式
重新設計上個範例,將IndexError及TypeError放在同一個except區塊裡面,但此種寫法便無法區隔是哪一種異常事件導致錯誤發生。
def print_city(index):
  city_list = ["台北市", "新北市", "台中市"]

  try:
    city = city_list[index]
  except (IndexError, TypeError):
    print("索引值超出範圍 或 型別錯誤!")
  else:
    print(city)
  finally:
    print("執行完畢!")

print_city(3)
print_city("a")
執行結果:
索引值超出範圍 或 型別錯誤!
執行完畢!
索引值超出範圍 或 型別錯誤!
執行完畢!

使用內建錯誤訊息

針對上述無法區分異常事件的情況,我們可以導出Python內建的錯誤訊息,來協助分辨錯誤的類型,語法如下:
try:
  可能需要捕捉異常情況的指令
except (異常事件1, 異常事件2, ......) as error_message:
  異常事件發生時的處理方式
改良上述程式,使用error_message將錯誤訊息儲存起來,就可以在發生IndexError時,印出「list index out of range」,而在發生TypeError時,印出「list indices must be integers or slices, not str」等不同的錯誤訊息。
def print_city(index):
  city_list = ["台北市", "新北市", "台中市"]

  try:
    city = city_list[index]
  except (IndexError, TypeError) as error_message:
    print(f"{error_message}")
  else:
    print(city)
  finally:
    print("執行完畢!")

print_city(3)
print_city("a")
執行結果:
list index out of range
執行完畢!
list indices must be integers or slices, not str
執行完畢!

自定義異常

除了讓Python直譯器自行判斷異常外,我們也可以使用raise語法自行定義異常事件,這個異常事件可以是Python判斷為正常的情況。如以下的程式碼中,我們把索引值大於1就定義為異常,但實際上索引值為2對於Python而言也是合理的。
def print_city(index):
  city_list = ["台北市", "新北市", "台中市"]

  if index > 1:
    raise IndexError("索引值超出範圍")

print_city(3)
執行後,Python會根據我們所自定義的異常事件來丟出Traceback訊息。
Traceback (most recent call last):
  File "C:\Users\wjweng\PycharmProjects\marathon_python\Day33\main.py", line 6, in 
    print_city(2)
  File "C:\Users\wjweng\PycharmProjects\marathon_python\Day33\main.py", line 4, in print_city
    raise IndexError("索引值超出自訂範圍")
IndexError: 索引值超出自訂範圍
定義了異常事件後,我們同樣可以使用上面提過的所有異常處理程序來處理自定義的異常事件,這裡就不再多重覆一次。

總結

俗話說「怕熱就不要進廚房」,對於寫程式的人來說,沒有遇過或解過bug的就不算真正寫過程式,重要的是如何培養debug的能力,以及如何對於可能預期的異常事件做出對應的預防處理。本篇算是這系列文章首次對於異常事件做初步的介紹,除了列出一些初學者常遇到的異常事件外,也說明了Python對於異常事件的處理程序,讓我們在寫作程式時,能減少一些讓程式異常終止的機會,增加一些容錯的能力。

程式範例

為什麼會看到廣告
Wei-Jie Weng
Wei-Jie Weng
留言0
查看全部
發表第一個留言支持創作者!