iT邦幫忙

2022 iThome 鐵人賽

DAY 17
0
自我挑戰組

從前端角度看30天學Python系列 第 17

【Day 17】例外處理

  • 分享至 

  • xImage
  •  
  • 例外與語法錯誤
  • Try...Except
  • Else
  • Finally
  • 額外補充: 拋出(Raising)例外.
  • 額外補充:AssertionError
  • 題外話

這篇文章是閱讀Asabeneh的30 Days Of Python: Day 17 - Exception Handling後的學習筆記與心得。

這章的原文中還有提到一些元組(tuple)和字典(dictionary)的操作,我想放在Day 18再寫,相對的,原文中沒有提到像是JavaScript中throw new Error()的操作,我參考了這篇文章加入今天的內容,當中一開頭提到:

例外與語法錯誤

語法錯誤(syntax error)像下方的print函式少了右括號關閉:

print(0/0

這類型的錯誤是不能用今天這章的例外(exceptions)處理來捕捉。


在JavaScript(以下簡稱JS)中會使用try...catch來避免一個錯誤發生整個程式就中斷了,在Python裡,則是使用try...except來做到這件事:

try:
	# 執行這塊程式碼
except:
	# try有錯誤發生時,執行這塊程式碼
else:
	# try結束沒有錯誤的話,執行這塊程式碼
finally:
	# 不管過程中是否有錯誤,最後一定會執行這塊程式碼

Try...Except

跟JS的 try...catch 相似,帶個例子來看:

try:
	print(10 + "5") # results TypeError
except:
	print("有錯誤") # 有錯誤

print(10 + "5")
# TypeError: unsupported operand type(s) for +: 'int' and 'str'

print("hello") # won't be executed

這樣會在終端(terminal)印出有錯誤,並可以看到在下一段執行同樣的程式碼會觸發TypeError,而且因為沒做例外處理,程式就中斷了,最後一行的print不會被執行到。

但可以注意到,原本Python內建的錯誤提示被我們自行撰寫的「有錯誤」給取代掉了,雖然可以透過,except Exception as err的方式拿到原本Python的錯誤描述,但描述中會少了error type,JS的話則會帶有error type的資訊。


Python中可以指定要抓取的錯誤類型,這點是JS中沒有的;修改一下上面的例子:

try:
    print(10 + "5") # results TypeError
    
except ValueError:
    print("有錯誤") # won't be executed

print("但程式沒有中斷")
  • 這個程式會中斷,因為在try當中,我們觸發的是TypeError但只有做ValueError的處理。
  • 可以再加上TypeError的例外,讓程式能順利執行完:
try:
    print(10 + "5") # results TypeError
    
except ValueError:
    print("有錯誤") # won't be executed

except TypeError:
    print("型別錯誤") # 型別錯誤

print("但程式沒有中斷") # 但程式沒有中斷

Else

在JS的例外處理中沒有這個行為,在這個子句(clause)中的程式碼會在try子句完成後沒有發生錯誤才會執行:

do_math() -> float 那個箭頭是型別提示,作用就像TypeScript標出函式的輸出那樣。 -- 參考這則回答

from random import random
import math

error_log = []

def do_math() -> float:
    id = math.floor(random() * 10000)
    error_log.append(f"{id} - do_math: start")
    result = 0
    try:
        num = int(input("Enter a number: "));
        result += math.pow(num, 2);
        
    except Exception as err:
        error_log.append(f"{id} - do_math: error - {err}")
        print(f"Exception: {err}")
        return do_math();
        
    else:
        error_log.append(f"{id} - do_math: end")
        return result;

foo = do_math()
print(foo)
print(error_log)

執行後操作及輸出如下(串列輸出有手動排版過):

Enter a number: g
Exception: invalid literal for int() with base 10: 'g'
Enter a number: 2
4.0
[
  '1068 - do_math: start',
  "1068 - do_math: error - invalid literal for int() with base 10: 'g'", 
  '7000 - do_math: start',
  '7000 - do_math: end'
]
  • 輸入"g"後發生錯誤,可以看到描述寫到"g"不能用在10進位的int()函式中。
  • 輸入2回傳4.0給變數foo並印出
  • 印出的error_log中可以看到do_math執行了兩次,一次是錯誤收尾,另一次則是順利結束函式。

Finally

這個finally不是指這篇文章的結束?,JS的錯誤處理中也有這個關鍵字(keyword),如文章開頭提到,這個子句(clause)會在函式最後執行:

把上面 else 例子中推送 error_log 的操作移到finally 中:

from random import random
import math

error_log = []

def do_math() -> float:
    id = math.floor(random() * 10000)
    error_log.append(f"{id} - do_math: start")
    result = 0
    try:
        num = int(input("Enter a number: "));
        result += math.pow(num, 2);
        
    except Exception as err:
        error_log.append(f"{id} - do_math: error - {err}")
        print(f"Exception: {err}")
        return do_math();
        
    else:
        return result;
        
    finally:
        error_log.append(f"{id} - do_math: end")

foo = do_math()
print(foo)
print(error_log)

執行後操作及輸出如下(串列輸出有手動排版過):

Enter a number: h
Exception: invalid literal for int() with base 10: 'h'
Enter a number: 3
9.0
[
  '4846 - do_math: start',
  "4846 - do_math: error - invalid literal for int() with base 10: 'h'", 
  '5201 - do_math: start',
  '5201 - do_math: end',
  '4846 - do_math: end'
]
  • 可以注意到跟else的例子在error_log的部份不同,第一次給錯誤型別的的do_math(4806),並沒有隨著錯誤發生而結束,在do_math(5201)結束後,do_math(4806)的finally還是有執行推送log。

額外補充:拋出(Raising)例外

Python中可以使用raise來產生例外,中斷程式:

month = int(input("Entering a number between 1 to 12: "))

if month < 1 or month > 12:
    raise Exception("Valid value is between 1 to 12. Entered value: {}, is invalid".format(month))

print("The value you entered is: {}".format(month))
  • 輸入13就會得到這段話:"Exception: Valid value is between 1 to 12. Entered value: 13, is invalid",並且會有Traceback的資訊指出錯誤產生的段落。

額外補充:AssertionError

使用assert依據判斷式在符合(True)時繼續程式,在不符合(False)時拋出AssertionError

month = int(input("Entering a number between 1 to 12: "))

assert(month >= 1 and month <= 12), "Valid value is between 1 to 12. Entered value: {}, is invalid".format(month)

print("The value you entered is: {}".format(month))
  • 輸入0會如預期地得到:"AssertionError: Valid value is between 1 to 12. Entered value: 0, is invalid",以及Traceback資訊。

題外話

Python中函式的宣告不像JS會hoisting,是由上而下執行,參考這則回答中的例子;若是在宣告前一函式前的段落就執行該函式會產生NameError

在JS中嘗試相同的操作並不會產生錯誤:

function print_sum(a, b) {
    console.log(sum_numbers(a, b))
}
print_sum(2, 4) // 6

function sum_numbers(a, b) {
    return a + b
}

最後寫了這個例子來總結這章的學習。


上一篇
【Day 16】日期與時間
下一篇
【Day 18】引數打包與開箱
系列文
從前端角度看30天學Python30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言