Python 初學第十一講—錯誤與例外處理

如何處理並且避免發生錯誤

Yu-Hsuan Chou
ccClub
10 min readMay 21, 2019

--

在學習寫程式的過程中,無論是在寫程式的過程當中不小心寫出了 bug,或是在使用者端進行輸入時和我們的預期不同時產生了執行錯誤等等,像這些錯誤以及例外的發生是常有的事情。

那麼,為了能夠正確的找出程式的問題、避免錯誤的發生,此時我們所需要的就是例外處理的學習。

常見的例外類型

無論是寫什麼程式語言,當我們寫出一個程式以後,電腦都會需要先轉成電腦看得懂的語言 ,然後執行。有時候我們會在這個過程當中就遇到錯誤,或著我們稱為例外,因此,為了能夠成功解開 bug,學會如何解讀系統回傳的 Error Message 也是一個重要的技能。

SyntaxError

首先,在初學 Python 的過程當中,一開始容易遇到的就是 SyntaxError ,也就是所謂的語法錯誤。以 Python 來說,一般而言 SyntaxError 之所以會發生通常會來自於以下幾個可能性:

  1. if-else statementfor loop 、 函式宣告的 def 等等沒有加冒號
  2. 在判斷式當中,將 == 寫成 =
  3. 字串前後並未完整加上引號
  4. 除此之外還有很多很多可能性

儘管很容易遇到,也不用太灰心,通常編譯器所回傳的 Error Message 都會將哪一行出現了 SyntaxError 顯示給你,因此錯誤的處理十分簡單,也通常不會需要花太多時間。

舉例來說,如果發生的錯誤是在字串前後並未完整的加上引號,那麼將會回傳以下的 Error Message ,將會有以下的執行結果:

錯誤訊息的內容為:

NameError

接著,我們所要介紹的錯誤類型是 NameError ,相信大家也並不陌生。

所謂的 NameError 是指當電腦無法找到我們所指定的變數時產生的錯誤,通常發生在打錯變數名稱,或是使用了並未宣告過的變數的時候。

舉例來說,當我們使用了一個叫做 ccClub 的變數,卻尚未定義他時,就會出現找不到變數的錯誤:

Error message 為:

TypeError

除此之外,在初學 Python 時,同樣經常遇到的錯誤就是 TypeError 了。 TypeError 通常出現在當我們誤用了變數的資料型態的時候,比如說對著整數變數跑 for i in 變數 、試圖改變 string 字串的特定字元、對著兩個不同資料型態( intfloat 之間除外)使用 >、<、== 等等判斷用的運算子。

以最後一個情況為例,當我們今天在一個 int 與一個 string 之間使用 + 時,電腦會無法判斷此時應該要對這兩個資料做整數的加法,還是字串之間拼接的操作,因此會出現 TypeError

當我們寫的是 "1" + 1 ,電腦會認為應該要做字串拼接的操作,因此會出現如下的錯誤:

錯誤訊息內容為:

相反的,如果今天寫的是 1 + "1" ,那麼電腦就會給出不同的錯誤訊息:

而執行錯誤如下:

ZeroDivisionError

在處理數值資料時, ZeroDivisionError 是個可能需要特別注意的錯誤。顧名思義, ZeroDivisionError 指的是我們在進行運算時,拿 0 當成除數時所產生的錯誤。

為了提醒我們不能夠計算任何數值除以零,因此會出現如下的錯誤訊息:

IndexError

接下來我們要介紹的,是在使用 list 或是 string 時可能會遇到的 IndexError 。叫做 IndexError 的原因在於,這個錯誤來自於使用了錯誤的 index ,也就是可能使用到了可用範圍之外的 index

舉例來說,當今天我們有一個叫做 mylistlist ,內容物為 [1, 2, 3] 三個整數,當我們今天試圖要取 index3 的值時,因為此 index 無法使用,所以會產生如下的 Error Message

結果如下:

除此之外,還有一個與 IndexError 相似的錯誤,叫做 KeyError,只是 KeyError 通常發生在字典的使用上。

以上是幾種 Python 內建錯誤類型的介紹,除了我們所介紹的以外,還有相當多種內建的錯誤種類,在遇到時只需要仔細閱讀錯誤訊息,或是將之拿去搜尋,都能獲得解決的辦法。

Try-except 例外處理

隨著對於程式學習的深入,在我們開始著手編寫更大的程式以後,需要面對的資料越來越多元,程式的使用者可能也不再只是我們自己,希望這個程式能夠被其他人所使用的同時,我們當然不希望這個程式碼在執行的過程當中因為錯誤而中斷,而是能夠妥善的處理這些例外情況。

因此,我們這次要介紹的,就是用來處理例外的 statement—Try-except。

簡介 Try-except statement

首先,一個基本的 try-except 的結構如下:

意思是,我們先嘗試執行 try 底下的程式碼,如果無法順利執行,在過程中產生了例外,就會接著執行 except 下的程式碼。我們可以指定 except 是否要接住特定的例外,也可以在不確定可能產生什麼例外時,利用一個 except區塊統一處理。 except 的區塊也可以有很多個,可以依照個人需求進行使用。

這樣講或許有些模糊,讓我們來看個簡單的例子吧!

舉例來說,當我們今天只是想要簡單地請使用者輸入兩個數字,然後輸出兩者相除的結果時,可能會面對到兩種例外:一、使用者拿 0 作為除數,也就是剛才介紹過的 ZeroDivisionError 。二、使用者輸入錯誤,將數字輸入成英文字母或是其他無法預測的輸入。那麼,為了妥善地處理這兩種不同的例外情況,我們可能會寫出如下的程式碼:

首先,我們嘗試「取得使用者的輸入,並且印出兩者相除的結果」。如果在這個過程當中遇到除數為零的情況,也就是 ZeroDivisionError時,我們就需要輸出 "Cannot divide by zero" 以提醒使用者。另一方面,如果不是除數為零,但卻出現錯誤時,我們就輸出 "Wrong input" 來提醒使用者。

因此,當輸入為 32 時,因為可以正確地執行,因此輸出結果如下:

但是,當我們的輸入是 30 時,會遇到除數為零所產生的例外,也就是 ZeroDivisionError ,因此會進到第一個 except 區塊,得到了我們自己所寫的錯誤訊息 "Cannot divide by zero" ,執行結果如下:

當輸入為 3a 時,因為我們無法將 a 轉為數字,而且此例外也並非上一個區塊指定的例外 ZeroDivisionError,因此會印出訊息 "Wrong input" ,執行結果如下:

More about try-except

當然, tryexcept 的使用並不僅如此,為了完成一個結構更加完整的程式,我們可能會利用一個充分使用 try-except 的結構,大致的長相如下:

除了剛才介紹過的 tryexcept 以外,我們也可以再搭配 else 以及 finally 兩個關鍵字進行程式的編寫。當 try 當中的程式可以正確地執行,將會執行 else 區塊當中的程式碼;而無論 try 區塊中的程式碼執行以後有沒有產生例外,都要執行 finally 區塊中的程式碼。

我們同樣使用剛才的範例,試著做一個進行除法運算的小程式,只是這次要多加兩件事情:一、如果可以正確地執行,要印出 "The answer is 答案" 。二、在整個程式執行結束以後,要印出 "Program finished"

那麼,程式碼的長相可能如下:

和剛才執行時的順序相同,會先執行 try 當中的程式碼,這裡為了方便之後的操作,我們將 a/b 的結果先 assign 給新的變數 c ,接著如果遇到例外的話,會執行 except 區塊,否則將會執行 else 當中的程式碼。

接著,因為這一次的程式碼包含了 finally 的區塊,所以在 except 或是 else中的程式碼執行結束以後,會接著執行 finally 當中的程式,印出 "Program finished"

所以,當我們的輸入是 30 時,會遇到 ZeroDivisionError ,因此會印出 "Cannot divide by 0" ,接著再印出 finally 區塊中的訊息。執行結果如下圖:

而當我們的輸入是 32 時,可以正確地執行 try 當中的程式碼,所以會執行 else 當中的程式碼,因此得到 "The answer is 1.5" ,以及執行 finally 區塊得到的 "Program finished"

執行結果如下:

至此,我們已經完成了例外處理的基本教學,無論是已知或是未知的例外我們都能夠妥善地進行處理。

這或許目前為止看起來不像是一個經常使用的概念,但是隨著程式開發的規模更大、功能更多,好的例外處理也顯得相當重要,因此,例外處理的學習同樣需要被重視。

--

--