一、說明
關於異常捕獲try-except:在學java的時候就被教育異常捕獲也是java相對c的一大優點,幾年下來多少也寫了些代碼,但異常捕獲總只得其形未得其神,在自己這只是讓發生錯誤的程序在不必要終止時不終止而已。
關於主動拋出異常raise:前段時間看到robot framework判斷測試用例運行失敗,是監控自己使用raise主動拋出的異常,這才有了主動拋出異常的概念。
關於斷言assert:前段時間寫了個模糊測試工具,然后發現很多可以導致宕機的問題,開發排查后說是新加的斷言導致的問題;當時對斷言並不太清楚,他們修復問題后也沒深入追究。
其實這里最主要是說,了解之后發現,try-except、raise和assert其實有相當大的關系,有必要記一記。
二、異常捕獲try-except
異常捕獲沒有很多說的,各語言意思都差不多只是書寫格式有點區別,我們直接上示例:
def testTryExcept(): try: file_obj = open('myfile.txt') str_var = file_obj.readline() int_var = int(str_var.strip()) # 如果檢測到是OSError類異常,進行以下處理 # OSError as err表示給當前捕獲到的OSError異常起別名為err;名字叫什么可以是隨意的 except OSError as err: print(f"OS error: {err}") # 如果不是OSError檢測到是ValueError,進行以下處理 except ValueError: print("Could not convert data to an integer.") # 如果既不是OSError也不是ValueError而是其他異常,進行以下處理 # Exception as e表示給當前捕獲到的異常起別名為e;名字叫什么可以是隨意的 # 如果不需要打印e,那么Exception as e這部分可省略 # 我自己而言,不會捕獲具體的異常類型,即不會像上面一樣單獨捕獲OSError和ValueError,就只寫下邊這么一個except就完了 except Exception as e: print(f"Unexpected error: {e}") # 前邊的print(f"{e}")形式只會打印異常的概要信息,如果要打印異常的堆棧信息要使用別的寫法 # 方法一:traceback # import traceback # print(f"{traceback.format_exc()}") # 方法二:logging.exception() # import logging # logging.exception(e) # 不管是否發生異常,finally部分都會執行 # 對於異常捕獲而言,finally部分經常可以沒有,至少一直以來我都不怎么寫 finally: file_obj.close()
三、主動拋出異常raise
在上面的try-except中我們都是被動等待異常出現然后進行捕獲----事實上這些被動等待的異常本質上也是庫函數使用raise主動拋出的----我們完全可以使用raise主動拋出異常,進一步說我們可以使用raise拋出自己定義的異常。
主動拋出異常的好處,一是可以拋出在語法上不被認為是異常但在功能上我們認為是異常的情況(如用戶名密碼錯誤等),二是可以自定義自己的異常報錯語句更方便異常的定位和排查。
注意,主動拋出的異常扔是異常,所以仍可以用try-except來捕獲。
# 自定義的異常類都要繼承Exception類,至少是間接繼承Exception類 class PasswordException(Exception): # 在init方法中定義一個password變量 def __init__(self,password): self.password = password def __str__(self): return repr(self.password) def testRaise(): # 主動拋出異常示例 try: username = input("please enter your username:") # 輸入的用戶名不是admin就拋出異常Exception if username != "admin": raise Exception(f"maybe your privilege is not enough: {username}") # 不過要注意這樣raise一個Exception會丟掉異常的堆棧信息,如果要原樣返回異常可以直接寫raise # raise # 可以看到打印的是我們自定義的異常語句 except Exception as e: print(f"{e}") # 主動拋出自定義異常示例 try: password = input("please enter your password:") # 輸入的密碼不是123456就拋出自定的的PasswordException異常 if password != "123456": raise PasswordException(password) # 我們自定義的異常有password變量,所以我們可以直接選擇把變量打印出來 except PasswordException as e: print(f"PasswordException: {e.password}") if __name__ == "__main__": testRaise()
四、斷言assert
4.1 assert本質討論
更多參見官方文檔:https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement
assert使用形式如下:
assert expression ["," expression]
如果只接一個表達示,那相當於如下:
if __debug__: if not expression: raise AssertionError
如果接兩個表達示,那相當於如下:
if __debug__: if not expression1: raise AssertionError(expression2)
其中涉及的__debug__和AssertionError如下:
__debug__:如果程序運行時不帶-O參數,則為True;反之則為False。
AssertionError:就是一個繼承Exception類的異常類,其源代碼在builtins.py中,如下圖所示
所以,本質上,assert就是raise的一個宏定義;當前緊接的好個表達示不為True時,就拋出異常。
4.2 assert的使用
assert經常用於參數被使用前的檢查操作,如果檢查未通過則直接拋出異常及早發現錯誤,避免明顯錯誤的參數還被往后傳遞。
注意,由於assert本質上還是raise,所以一樣可以使用try-except捕獲,而不是說斷言錯誤程序就一定會終止。
def testAssert(): try: int_var = int(input("please enter a positive number:")) # 如果輸入的數值不大於0,斷言失敗,拋出異常 assert int_var > 0 except: print(f"sorry, please enter a positive number") print(f"what you enter is: {int_var}") if __name__ == "__main__": testAssert()
參考:
https://www.runoob.com/python3/python3-errors-execptions.html