處理例外狀況
當您第一次發現將大型追蹤顯示為輸出的例外狀況時,您可能會想要攔截每個錯誤,以避免發生該錯誤。
如果您在 Mars 的任務中,若導覽系統上的文字顯示「發生錯誤」,您該怎麼做? 假設沒有其他資訊或內容,只有閃爍的紅燈與錯誤文字。 身為開發人員,將自己放在程式的另一端很有用:發生錯誤時,使用者可以做什麼?
雖然本課程模組涵蓋如何藉由攔截例外狀況來進行處理,但不需要隨時攔截例外狀況。 有時候,讓例外狀況引發很有用,其他呼叫者即可處理錯誤。
嘗試和例外區塊
讓我們使用導覽器範例來建立程式碼,以開啟 Mars 任務的組態檔。 組態檔可能有各種問題,因此當問題出現時,精確回報問題非常重要。 我們知道,如果檔案或目錄不存在,就會引發 FileNotFoundError
。 如果我們想要處理該例外狀況,可以使用 try
和 except
區塊來執行該作業:
try:
open('config.txt')
except FileNotFoundError:
print("Couldn't find the config.txt file!")
Couldn't find the config.txt file!
在 try
關鍵字之後,您會新增可能造成例外狀況的程式碼。 接下來,您會新增 except
關鍵字以及可能的例外狀況,後面接著任何需要在該情況發生時執行的程式碼。 因為 config.txt 不存在於系統中,所以 Python 會列印組態檔不存在。 try
和 except
區塊以及有用的訊息會防止追蹤,但仍會通知使用者相關問題。
雖然不存在的檔案很常見,但這不是您可能發現的唯一錯誤。 無效的檔案權限可能會防止讀取檔案,即使檔案存在也一樣。 讓我們在 Visual Studio Code 中建立名為 config.py 的新 Python 檔案。 將下列程式碼新增至尋找並讀取導覽系統的組態檔的檔案:
def main():
try:
configuration = open('config.txt')
except FileNotFoundError:
print("Couldn't find the config.txt file!")
if __name__ == '__main__':
main()
接著,建立名為 config.txt 的目錄。 嘗試呼叫 config.py 檔案,以查看應該與此類似的新錯誤:
python3 config.py
Traceback (most recent call last):
File "/tmp/config.py", line 9, in <module>
main()
File "/tmp/config.py", line 3, in main
configuration = open('config.txt')
IsADirectoryError: [Errno 21] Is a directory: 'config.txt'
處理此錯誤的沒用方法會是攔截所有可能的例外狀況,以防止追蹤。 如需了解擷取所有例外狀況的原因,請藉由更新 config.py 檔案中新建立的 main()
函式來嘗試:
def main():
try:
configuration = open('config.txt')
except Exception:
print("Couldn't find the config.txt file!")
現在,在 config.txt 目錄所在且具有不當權限的相同位置,再次執行程式碼:
python3 config.py
Couldn't find the config.txt file!
現在問題在於錯誤訊息不正確。 目錄確實存在,但其具有不同的權限,Python 無法予以讀取。 當您處理軟體錯誤時,有下列錯誤可能會使人沮喪:
- 未指出真正的問題為何。
- 提供不符合實際問題的輸出。
- 未提示可採取哪些動作來修正問題。
讓我們修正這一段程式碼,以解決上述所有挫折。 恢復為攔截 FileNotFoundError
,然後新增另一個 except
區塊以攔截 PermissionError
:
def main():
try:
configuration = open('config.txt')
except FileNotFoundError:
print("Couldn't find the config.txt file!")
except IsADirectoryError:
print("Found config.txt but it is a directory, couldn't read it")
現在,在 config.txt 目錄所在的同一位置再次加以執行:
python3 config.py
Found config.txt but couldn't read it
現在刪除 config.txt 目錄,以確保會改為觸達第一個 except
區塊:
rm -f config.txt
python3 config.py
Couldn't find the config.txt file!
當錯誤屬於類似的本質,而且不需要個別進行處理時,您可以在 except
行中使用括弧,將例外狀況群集在一起。 例如,如果導覽系統負載沉重,而且檔案系統變得太忙碌,則一起攔截 BlockingIOError
和 TimeOutError
會很合理:
def main():
try:
configuration = open('config.txt')
except FileNotFoundError:
print("Couldn't find the config.txt file!")
except IsADirectoryError:
print("Found config.txt but it is a directory, couldn't read it")
except (BlockingIOError, TimeoutError):
print("Filesystem under heavy load, can't complete reading configuration file")
提示
雖然您可以將例外狀況群集在一起,但只有在不需要個別處理例外狀況時,才這麼做。 避免將許多例外狀況群集在一起,以提供一般化錯誤訊息。
如果您需要存取與例外狀況相關聯的錯誤,您必須更新 except
行以包含 as
關鍵字。 如果例外狀況太過普通,則錯誤訊息可能很有用:
try:
open("mars.jpg")
except FileNotFoundError as err:
print("Got a problem trying to read the file:", err)
Got a problem trying to read the file: [Errno 2] No such file or directory: 'mars.jpg'
在此情況下,as err
表示 err
變成以例外狀況物件作為值的變數。 然後,它會使用此值來列印與例外狀況相關聯的錯誤訊息。 使用此技巧的另一個原因是直接存取錯誤的屬性。 例如,如果您攔截的是更為普通的 OSError
例外狀況,也就是 FilenotFoundError
和 PermissionError
的父代例外狀況,您可經由 .errno
屬性來分辨它們:
try:
open("config.txt")
except OSError as err:
if err.errno == 2:
print("Couldn't find the config.txt file!")
elif err.errno == 13:
print("Found config.txt but couldn't read it")
Couldn't find the config.txt file!
一律嘗試使用可為程式碼提供最佳可讀性並協助未來維護的技巧。 有時候,必須使用較不易讀取的程式碼,以在錯誤發生時提供更好的使用者體驗。