|
文章目錄
- 異常處理
- 1. Python異常
- 2. 異常捕獲
- try-except語句
- 捕獲所有的異常信息
- 獲取異常對象
- finally塊
- 3. raise語句
- 4. 自定義異常
- 5. 函數調用里面產生的異常
- 補充練習

異常處理
1. Python異常
Python異常指的是在程序執行過程中發生的錯誤或異常情況. 當代碼遇到錯誤時, 會引發異常并中斷程序的正常執行流程.
異常提供了一種機制來處理錯誤, 以便程序可以在錯誤發生時采取適當的行動, 而不會導致程序崩潰或產生意外結果.
在Python中, 異常是通過異常類表示的. 每個異常類都是Python內置的或自定義的, 用于表示特定類型的錯誤. 常見的內置異常類包括:
SyntaxError
:語法錯誤, 通常是代碼書寫不正確.NameError
:名稱錯誤, 嘗試訪問不存在的變量或函數.TypeError
:類型錯誤, 操作或函數應用于不兼容的數據類型.ValueError
:值錯誤, 當函數接收到不合法的值時引發.ZeroDivisionError
:零除錯誤, 嘗試將一個數除以零.Exception
:所有內置異常類的基類, 即其他所有的異常都是基于它的.
除了這些內置異常類, Python還提供了許多其他的異常類, 用于特定的錯誤情況. 可以使用try-except
語句來捕獲和處理異常. 通過在try
塊中編寫可能引發異常的代碼, 并在except
塊中處理異常, 可以保護程序免受錯誤的影響(在程序出現異常時不會直接中斷程序執行), 而是采取適當的措施來處理異常情況.
異常處理可以幫助我們提高程序的健壯性和容錯能力, 使得程序能夠更好地處理異常情況, 并給出恰當的反饋或采取相應的措施.
2. 異常捕獲
try-except語句
在Python中, 我們使用try-except
語句來捕獲和處理異常. try
塊用于編寫可能引發異常的代碼, 而except
塊用于處理異常情況.
try:# 可能引發異常的代碼塊
except ExceptionType:# 處理異常的代碼塊
try
塊中的代碼會按順序執行, 如果發生異常, 則會跳轉到匹配的except
塊.except
塊中的代碼會處理特定類型的異常,ExceptionType
表示要捕獲的異常類型.- 可以有多個
except
塊, 每個塊可以處理不同類型的異常, 或者使用一個塊來處理多個異常類型.
def divide_numbers(a, b):result = a / bprint("result", result)divide_numbers(10, 2) # 5.0
divide_numbers(10, 0) # 觸發除零異常, 程序中斷, 后續代碼都不會被執行 - 這樣的話代碼的容錯能力很低
divide_numbers(9, 2)
divide_numbers(8, 2)
def divide_numbers(a, b):try:result = a / bprint("result", result)except ZeroDivisionError:print("0不能作為除數.")except TypeError:print("參數傳遞類型錯誤.")except Exception as e:print(f"程序出現了意外錯誤: 具體出錯內容: {e}.")finally:print(f"{a}/{b}的結果計算完畢.")divide_numbers(10, 2) # 5.0
divide_numbers(10, 0) # 顯示‘0不能作為除數’
divide_numbers(9, 2) # 4.5
divide_numbers(8, 2) # 4.0
捕獲所有的異常信息
# ---------方式一----------try:# 可能引發異常的代碼塊except:# 處理所有異常def divide_numbers(a, b):try:result = a / bexcept:print("程序出錯了.")# ---------方式二----------# 根據基類Exception來捕獲def divide_numbers(a, b):try:result = a / bexcept Exception:print("程序出錯了.")
獲取異常對象
在異常處理塊中, 可以使用as
關鍵字將異常信息賦給一個變量, 以便進一步處理或打印異常信息.
try:# 可能引發異常的代碼塊except ExceptionType as e:# 處理異常, 并使用變量e訪問異常信息def divide_numbers(a, b):try:result = a / bexcept Exception as e: # 注意: 根據基類Exception來捕獲異常只能放在最后不能放在其他異常類之前print(f"程序出現了意外錯誤: 具體的出錯內容: {e}")
else塊
else
塊是try-except
語句的可選部分, 用于指定在try
塊中的代碼執行完畢且沒有引發異常時要執行的代碼. 如果在try
塊中發生了異常并被捕獲, 則不會執行else
塊中的代碼.
try:# 可能引發異常的代碼塊except ExceptionType:# 處理異常的代碼塊else:# 在沒有發生異常時執行的代碼塊, 發生異常時就不會執行
注意點: 在try里如果return了, 即使沒有報錯, 也不會執行else里的代碼
# 示例代碼, 大家自己跑到pycharm里運行的試試.def test():try:# 執行一些操作result = 3return result # 在這里返回結果,函數立即退出except ValueError:# 處理異常passelse:# 這里的代碼不會執行print("This code will not be executed.")# 調用函數result = test()print(result)
使用 else
塊可以提高代碼的可讀性和可維護性, 但并不是所有人都認同這種做法.有些開發者更傾向于只使用 try-except
塊, 將異常處理的邏輯放在一起, 使代碼更加緊湊.總之, 使用 try-else
結構是一種代碼組織的選擇, 而不是強制性的規范.在實際開發中, 我們應根據代碼的需求、可讀性和一致性等因素來決定是否使用 else
塊
很多大牛在開發中幾乎不使用 else, 因為對代碼的可讀性并沒有多大的提升.
所以了解即可.
finally塊
除了try-except
語句, 還可以使用finally
(可選的)塊來執行無論是否發生異常都需要執行的代碼. finally塊中的代碼在try塊中的代碼執行完畢后無論是否發生異常都會執行.
try:# 可能引發異常的代碼塊except ExceptionType:# 處理異常的代碼塊finally:# 無論是否發生異常, 都會執行的代碼塊def divide_numbers(a, b):try:result = a / bprint("result", result)except ZeroDivisionError:print("0不能作為除數.")except TypeError:print("參數傳遞類型錯誤.")except Exception as e:print(f"程序出現了意外錯誤: 具體出錯內容: {e}.")finally:print(f"{a}/{b}的結果計算完畢.")
由于finally塊來執行無論是否發生異常都會執行的代碼塊, 所以經常用來處理資源回收.
try:# 沒有使用with語句打開文件的話, 需要手動關閉文件f = open('./contact1.txt', 'r')f.close()
except FileNotFoundError:print("不存在此文件")# 上面的代碼是有問題的
# 如果打開文件的時候出錯了, 比如當前目錄下沒有這個文件就會導致異常
# 打開文件導致異常后就不會執行f.close()關閉文件
# 但是有一點還需要格外注意就是如果是由于沒有找到這個文件觸發異常就會在打開文件的時候觸發異常然后直接跳到except FileNotFoundError執行后面的代碼, 由于表達式的執行是先計算右邊的值然后再將計算結果賦值給左邊, 這里由于打開文件觸發異常導致沒有執行賦值語句, 所以此時f變量是不存在的, 后續調用f.close()是沒有意義的.# 還有一種情況是, 如果打開文件沒有問題, 但是在后續處理異常時出現問題
# 這個時候就可以使用finally來關閉文件
try:f = open('./contact.txt', 'r')content = f.read()# 后續處理# ...
except FileNotFoundError:print("不存在此文件")
except Exception:print("后續處理異常")
finally:f.close()
# 如果在函數體的try代碼塊中有return語句和finally, 它們的執行順序是怎樣的:
def divide_numbers(a, b):try:result = a / breturn resultexcept ZeroDivisionError:print("0不能作為除數.")except TypeError:print("參數傳遞類型錯誤.")except Exception as e:print(f"程序出現了意外錯誤: 具體出錯內容: {e}.")finally:print(f"{a}/{b}的結果計算完畢.")res1 = divide_numbers(10, 2)# 執行結果
# 10/2的結果計算完畢.
# 5.0# 從上面的執行結果說明了:
# 即使在函數定義里的try代碼塊中使用了return語句還是會執行后面finally代碼塊中的內容
總結
異常通常用于處理意外或異常的情況, 即那些無法在代碼中預測和處理的情況.
當你無法準確預測可能出現的錯誤或無法在當前上下文中處理錯誤時, 拋出異常是一種更合適的方式.
異常提供了一種機制, 讓你能夠在錯誤發生時終止當前的代碼執行, 并將控制權交給上層代碼或異常處理機制
3. raise語句
raise語句是 Python 中的一個關鍵字, 用于手動引發異常.
(前面都是自動觸發異常)
通過 raise 語句, 我們可以選擇性地在代碼中引發特定類型的異常, 并提供相應的錯誤信息.這樣, 我們就能夠在需要的時候中斷程序的正常執行流程, 并進行適當的異常處理.
語法:
raise [ExceptionType([args])]# ExceptionType 是要引發的異常類型.# args 是一個可選參數, 用于向異常類傳遞附加的信息.# 如果只寫一個raise, 則會默認引發RuntimeError.# raise語句后的代碼不會執行.(類似return)
def check_age(age):if age < 18:raise ValueError('年齡小于18歲, 不允許注冊.') # 手動拋出異常else:# 注冊代碼如下:passprint('恭喜, 注冊成功')try:age = int(input("請輸入年齡:"))check_age(age)
except ValueError as e:print(e) # 可以把在上面的raise中指定的拋出異常的信息打印出來, 請細品raise的使用 - 可以Chat一下# 執行結果:
# 請輸入年齡:17
# 年齡小于18歲, 不允許注冊.
使用注意事項:
- 選擇適當的異常類型:根據具體的情況, 選擇合適的異常類型來反映錯誤的性質. Python 提供了許多內置的異常類, 如
ValueError
、TypeError
、FileNotFoundError
等, 可以根據需要選擇合適的異常類或自定義異常類.- 提供明確的錯誤信息:在引發異常時, 盡量提供清晰、明確的錯誤信息, 以便在程序出錯時能夠準確地定位和理解錯誤的原因.
- 在適當的位置引發異常:raise語句應該放置在程序邏輯中合適的位置, 以便在需要時引發異常.根據代碼的要求, 可以在函數、方法或其他控制流結構中使用 raise 語句.
- 捕獲和處理異常:使用
try
-except
塊捕獲和處理引發的異常.根據具體的異常類型, 編寫相應的錯誤處理代碼, 以便優雅地處理異常情況.- 避免濫用 raise 語句:raise 語句應該用于合適的情況, 不應該濫用.只有在必要的時候才使用 raise語句, 以避免引發不必要的異常.
4. 自定義異常
異常類型都是 繼承自Exception的類,表示各種類型的錯誤.
我們也可以自己定義異常,比如我們寫一個用戶注冊的函數, 要求用戶輸入的電話號碼只能是中國的電話號碼,并且電話號碼中不能有非數字字符.
可以定義下面這兩種異常類型:
# 異常對象,代表電話號碼有非法字符
class InvalidCharError(Exception):pass# 異常對象,代表電話號碼非中國號碼
class NotChinaTelError(Exception):pass
定義了上面的異常,當用戶輸入電話號碼時,出現相應錯誤的時候,我們就可以使用raise 關鍵字來拋出對應的自定義異常.
def register():tel = input('請注冊您的電話號碼:')# 如果有非數字字符if not tel.isdigit(): raise InvalidCharError()# 如果不是以86開頭,則不是中國號碼if not tel.startswith('86'): raise NotChinaTelError()return teltry:ret = register()
except InvalidCharError:print('電話號碼中有錯誤的字符')
except NotChinaTelError:print('非中國手機號碼')
5. 函數調用里面產生的異常
請看下面這段代碼:
def level_3():print ('進入 level_3')a = [0]b = a[1]print ('離開 level_3')def level_2():print ('進入 level_2')level_3()print ('離開 level_2')def level_1():print ('進入 level_1')level_2()print ('離開 level_1')level_1()print('程序正常退出')
運行該代碼會得到類似下面的結果:
進入 level_1
進入 level_2
進入 level_3
Traceback (most recent call last):File "E:\err.py", line 18, in <module>level_1()File "E:\err.py", line 14, in level_1level_2()File "E:\err.py", line 9, in level_2level_3()File "E:\err.py", line 4, in level_3b = a[1]
IndexError: list index out of range
函數調用次序是這樣的
主體部分調用 函數 level_1
函數level_1調用 函數level_2
函數level_2調用 函數level_3
大家注意:函數 level_3 中有個 列表索引越界的錯誤.
所以執行到該函數的時候,解釋器報錯了。它在終端上顯示了錯誤代碼的具體位置. 也就是:
File "E:\err.py", line 4, in level_3b = a[1]
大家可以發現,上面還有輸出的信息,說明了這行引起異常的代碼, 是怎樣被 一層層 的調用進來的.
這就是函數調用棧的信息.
當異常在函數中產生的時候,解釋器會終止當前代碼的執行, 查看當前函數是否 聲明了該類型異常的 except 處理,如果有,就執行, 隨后繼續執行代碼.
如果當前函數沒有 聲明了該類型異常的處理, 就會中止當前函數的執行,退出到調用該函數的上層函數中, 查看上層是否有 聲明了該類型異常的 except 處理. 如果有,就執行該異常匹配處理. 隨后繼續執行代碼.
如果上層函數也沒有 該類型異常的匹配處理, 就會到繼續到再上層的函數查看是否有 該類型異常的匹配處理.
如此這般,直到到了最外層的代碼. 如果依然沒有 聲明了該類型異常處理,就終止當前代碼的執行.
補充練習
案例: 模擬用戶名校驗.
def check_username(username):"""1. 長度不能小于5.2. 只能包含字符.3. 禁止使用系統用戶名. admin, root.:param username: 傳入的用戶名.:return: None."""if username in ['admin', 'root']:raise ValueError('禁止使用系統用戶名')if len(username) < 5:raise ValueError('用戶名長度小于5')if not username.isalpha():raise ValueError('用戶名只能包含字符')# 如果上面的3個判斷都沒進, 就會走到這里.print(f'{username}校驗成功.')try:username = input("請輸入用戶名:")check_username(username)
except ValueError as e:print(e)
"""
對之前寫的通訊錄加上異常處理機制 使用異常捕獲完善. 使其健壯性更強.
1. 如果用戶輸入了我們規則之外的指令. '1 2 3 4 5'以外的. 應該如何處理.
2. 讀寫我們上節課使用的'r+', 實際上讀跟寫要分離開, 才是最好的.
3. 若是我們本地沒有通訊錄文件的時候, 第一次讀取, 會出錯, 如何處理.
4. 刪除聯系人時, 如果用戶輸入錯了想要刪除的人, 如想要刪除'張', 輸入成了'劉', 容易出現誤刪, 而且會出現刪除通訊錄里不存在的人. 應該如何處理.
5. 手機號是不存在字母的, 如果用戶輸入有字母的話, 應該如何處理.
"""
import jsondef read_file():try:with open("./contact.txt", 'r', encoding='utf8') as f:content = f.read()return contentexcept FileNotFoundError:return Falsedef write_file(content):with open("./contact.txt", 'w', encoding='utf8') as f:f.write(content)def query_all():content = read_file()if content:# 如果存在, 就要展示所有的聯系人.try:json_data = json.loads(content)if not json_data:print(f'該通訊錄目前沒有聯系人, 請先添加聯系人')returnexcept json.decoder.JSONDecodeError:print('系統出錯, 稍后在嘗試.')# 給開發人員發出提醒. 趕緊去修復.returnelse:print('為您查詢到的所有聯系人如下:')for key, val in json_data.items():print(f'{key}: {val}')else:print(f'該通訊錄目前沒有聯系人, 請先添加聯系人')def query_contact():name = input("請輸入想要查詢的聯系人姓名:")content = read_file()if content:# 如果存在, 就要做查詢操作.json_data = json.loads(content)phone = json_data.get(name)print(f'為您查詢到的指定聯系人{name}的電話是: {phone}')else:print(f'未找到指定聯系人: {name}')def del_contact():name = input("請輸入想要刪除的聯系人姓名:")content = read_file()if content:# 如果存在, 就要做刪除操作.res = input(f"是否確認刪除聯系人; {name}. y/n?")if res.lower() == 'y':json_data = json.loads(content)# pop_value = json_data.pop(name, None)try:json_data.pop(name)except KeyError:print('沒有找到指定的聯系人.')returnwrite_file(json.dumps(json_data, ensure_ascii=False))print(f'刪除{name}成功')else:print('已取消刪除操作')else:# 如果不存在, 就沒辦法刪了.print(f'未找到指定聯系人: {name}')def add_contact():name = input("請輸入聯系人姓名:")phone = input("請輸入聯系人電話:")try:int(phone)except ValueError:print('您輸入的手機號并不是純數字的. 請重新添加')returncontact_dict = {name: phone}content = read_file()if content:json_data = json.loads(content)json_data.update(contact_dict)else:json_data = contact_dictwrite_file(json.dumps(json_data, ensure_ascii=False))print(f'添加{name}成功')def menu():print("歡迎使用通訊錄管理系統")print("------welcome-----")print("菜單選項:")print("1. 添加聯系人.")print("2. 刪除聯系人.")print("3. 查詢指定聯系人.")print("4. 查看所有的聯系人.")print("5. 退出通訊錄.")while True:try:choice = int(input("請輸入您想要操作的選項:"))except ValueError:print('請輸入1-2-3-4-5的對應指令.')continueif choice == 1:add_contact()elif choice == 2:del_contact()elif choice == 3:query_contact()elif choice == 4:query_all()elif choice == 5:print('已退出通訊錄管理系統')breakelse:print('請輸入1-2-3-4-5的對應指令.')menu()
|
|