第八章 異常
異常事件可能是錯誤(如試圖除以零),也可能是通常不會發生的事情。
Python提供功能強大的替代解決方案——異常處理機制。
異常是什么?
Python使用異常對象來表示異常狀態,并在遇到錯誤時引發異常。異常對象未被處理或捕 獲)時,程序將終止并顯示一條錯誤消息(traceback)。
1 / 0
'''
ZeroDivisionError Traceback (most recent call last)
<ipython-input-146-05c9758a9c21> in <module>()
----> 1/0
ZeroDivisionError: division by zero
'''
事實上,每個異常都是某個類(這里是ZeroDivisionError)的實例.
讓事情沿你指定的軌道出錯
raise 語句
要引發異常,可使用raise
語句,并將一個類(必須是Exception的子類)或實例作為參數。將類作為參數時,將自動創建一個實例。
在第一個示例(raise Exception)中,引發的是通用異常,沒有指出出現了什么錯誤。
在第二個示例中,添加了錯誤消息beyondhuangjiaju。
raise Exception
'''
Exception Traceback (most recent call last)
<ipython-input-147-fca2ab0ca76b> in <module>()
----> raise ExceptionException:
'''
raise Exception('beyondhuangjiaju')
'''
Exception Traceback (most recent call last)
<ipython-input-149-dfdac40c1137> in <module>()
----> raise Exception('beyondhuangjiaju')Exception: beyondhuangjiaju
'''raise ArithmeticError
'''
ArithmeticError Traceback (most recent call last)
<ipython-input-150-36d9a98b39c2> in <module>()
----> raise ArithmeticErrorArithmeticError:
'''
raise ArithmeticError("beyondhuangjiaju")
'''
ArithmeticError Traceback (most recent call last)
<ipython-input-151-a6ed875c1de3> in <module>()
----> 1 raise ArithmeticError("beyondhuangjiaju")ArithmeticError: beyondhuangjiaju
'''
一些內置的異常類如下表所示:
類名 | 描述 |
---|---|
Exception | 幾乎所有的異常類都是從它派生而來的 |
AttributeError | 引用屬性或給它賦值失敗時引發 |
OSError | 操作系統不能執行指定的任務(如打開文件)時引發,有多個子類 |
IndexError | 使用序列中不存在的索引時引發,為LookupError的子類 |
KeyError | 使用映射中不存在的鍵時引發,為LookupError的子類 |
NameError | 找不到名稱(變量)時引發 |
SyntaxError | 代碼不正確時引發 |
TypeError | 將內置操作或函數用于類型不正確的對象時引發 |
ValueError | 將內置操作或函數用于這樣的對象時引發:其類型正確但包含的值不合適 |
ZeroDivisionError | 在除法或求模運算的第二個參數為零時引發 |
自定義的異常類
如何創建異常類呢?
就像創建其他類一樣,但務必直接或間接地繼承Exception(這意味著從任何內置異常類派生都可以)。
當然,如果你愿意,也可在自定義異常類中添加方法。
class SomeCustomBeyondException(Exception): pass
捕獲異常
異常比較有趣的地方是可對其進行處理,通常稱之為捕獲異常
。
可使用try/except
語句。
try: x = int(input('Enter the first number: ')) y = int(input('Enter the second number: ')) print(x / y)
except ZeroDivisionError: print("The second number can't be zero!")'''
Enter the first number: 5
Enter the second number: 0
The second number can't be zero!
'''
不用提供參數
來看一個能夠“抑制”異常ZeroDivisionError的計算器類。如果啟用了這種功能,計算器將打印一條錯誤消息,而不讓異常繼續傳播。在與用戶交互的會話中使用這個計算器時,抑制異常很有用;但在程序內部使用時,引發異常是更佳的選擇(此時應關閉“抑制”功能)。
class MuffledCalculator: muffled = False def calc(self, expr): try: return eval(expr) except ZeroDivisionError: if self.muffled: print('Division by zero is illegal') else: raisecalculator = MuffledCalculator()
calculator.calc('10 / 2')#結果為:5.0
calculator.calc('10 / 0')#報錯!!!calculator.muffled = True
calculator.calc('10 / 0') #結果為:Division by zero is illegal
如果無法處理異常,在except子句中使用不帶參數的raise通常是不錯的選擇,但有時你可能想引發別的異常。
在這種情況下,導致進入except子句的異常將被作為異常上下文存儲起來,并出現在最終的錯誤消息中。
try:1/0
except ZeroDivisionError:raise ValueError
#在處理上述異常時,引發了另一個異常:'''
ZeroDivisionError Traceback (most recent call last)
<ipython-input-160-14da06562399> in <module>()1 try:
----> 2 1/03 except ZeroDivisionError:ZeroDivisionError: division by zeroDuring handling of the above exception, another exception occurred:ValueError Traceback (most recent call last)
<ipython-input-160-14da06562399> in <module>()2 1/03 except ZeroDivisionError:
----> 4 raise ValueErrorValueError:
'''#可使用raise ... from ...語句來提供自己的異常上下文,也可使用None來禁用上下文。
try:1/0
except ZeroDivisionError:raise ValueError from None'''
ValueError Traceback (most recent call last)
<ipython-input-161-f4775ad0e53d> in <module>()2 1/03 except ZeroDivisionError:
----> 4 raise ValueError from NoneValueError:
'''
多個 except 子句
當你在輸入字符串的時候,此時會引發另一種異常,該程序中的except子句只捕獲ZeroDivisionError
異常,這種異常將成為漏網之魚,導致程序終止。
try: x = int(input('Enter the first number: ')) y = int(input('Enter the second number: ')) print(x / y)
except ZeroDivisionError: print("The second number can't be zero!")'''
Enter the first number: 1999
Enter the second number: beyond
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-2-f93dbf72378b> in <module>()1 try:2 x = int(input('Enter the first number: '))
----> 3 y = int(input('Enter the second number: '))4 print(x / y)5 except ZeroDivisionError:ValueError: invalid literal for int() with base 10: 'beyond'
'''
為同時捕獲這種異常,可在try/except語句中再添加一個except子句。
try: x = int(input('Enter the first number: ')) y = int(input('Enter the second number: ')) print(x / y)
except ZeroDivisionError: print("The second number can't be zero!")
except TypeError:print("That wasn't a number,was it?")
一箭雙雕
如果要使用一個except子句捕獲多種異常,可在一個元組中指定這些異常
。
在except子句中,異常兩邊的圓括號很重要。
try: x = int(input('Enter the first number: ')) y = int(input('Enter the second number: ')) print(x / y)
except (ZeroDivisionError,TypeError,NameError): print("Your numbers were bogus ...")
在上述代碼中,如果用戶輸入字符串、其他非數字值或輸入的第二個數為零,都將打印同樣的錯誤消息。當然,僅僅打印錯誤消息幫助不大。
捕獲對象
try: x = int(input('Enter the first number: ')) y = int(input('Enter the second number: ')) print(x / y)
except (ZeroDivisionError,TypeError) as e: print(e)
這里的except子句捕獲兩種異常,但由于同時顯式地捕獲了對象本身,因此可將其打印出來,讓用戶知道發生了什么情況。
一網打盡
即使程序處理了好幾種異常,還是可能有一些漏網之魚。
如果用戶在提示時不輸入任何內容就按回車鍵,將出現一條錯誤消息,這種異常未被try/except語句捕獲,這理所當然,因為你沒有預測到這種問題,也沒有采取相應的措施。
try: x = int(input('Enter the first number: ')) y = int(input('Enter the second number: ')) print(x / y)
except: print("Something wrong happened...")'''
Enter the first number:
Something wrong happened...
'''
像這樣捕獲所有的異常很危險,因為這不僅會隱藏你有心理準備的錯誤,還會隱藏你沒有考慮過的錯誤。
在大多數情況下,更好的選擇是使用except Exception as e
并對異常對象進行檢查。
SystemExit和KeyboardInterrupt從**BaseException(Exception的超類)**派生而來
萬事大吉
使用except Exception as e
的小技巧在這個小型除法程序中打印更有用的錯誤消息。
while True:try:x = int(input('Enter the first number: ')) y = int(input('Enter the second number: ')) value = x / yprint("x / y is",value)except Exception as e:print("Invalid input:",e)print('Please try again')else:break'''
Enter the first number: 1014
Enter the second number: beyond
Invalid input: invalid literal for int() with base 10: 'beyond'
Please try again
Enter the first number: 1202
Enter the second number:
Invalid input: invalid literal for int() with base 10: ''
Please try again
Enter the first number: 1014
Enter the second number: 0522
x / y is 1.9425287356321839
'''
最后
最后,還有finally
子句,可用于在發生異常時執行清理
工作。這個子句是與try子句配套的。
finally子句非常適合用于確保文件或網絡套接字等得以關閉。
不管try子句中發生什么異常,都將執行finally子句。
x = None
try:x = 1 / 0
except NameError:print("Unknown variable")
else:print("That went well!")
finally:print('Cleaning up ...')del x
異常和函數
異常和函數有著天然的聯系。
如果不處理函數中引發的異常,它將向上傳播到調用函數的地方。
如果在那里也未得到處理,異常將繼續傳播,直至到達主程序(全局作用域)。
如果主程序中也沒有異常處理程序,程序將終止并顯示棧跟蹤消息。
def faulty():raise Exception('Something is wrong')def ignore_exception():faulty()def handle_exception():try:faulty()except:print('Exception handled')ignore_exception()
'''
Exception Traceback (most recent call last)
<ipython-input-6-5ac314d0ac0c> in <module>()
----> 1 ignore_exception()<ipython-input-5-6806e60d5602> in ignore_exception()3 4 def ignore_exception():
----> 5 faulty()6 7 def handle_exception():<ipython-input-5-6806e60d5602> in faulty()1 def faulty():
----> 2 raise Exception('Something is wrong')3 4 def ignore_exception():5 faulty()Exception: Something is wrong
'''handle_exception()#結果為:Exception handled
faulty中引發的異常依次從faulty和ignore_exception向外傳播,最終導致顯示一條棧跟蹤消息。
調用handle_exception時,異常最終傳播到handle_exception,并被這里的try/except語句處理。
異常之禪
假設有一個字典,你要在指定的鍵存在時打印與之相關聯的值,否則什么都不做。
def describe_person(person):print('Description of',person['name'])print('Age:',person['age'])if 'occupation' in person:print('Occupation:',person['occupation'])
上面的這段代碼很直觀,但效率不高(雖然這里的重點是代碼簡潔),
因為它必須兩次查找occupation’鍵:
一次檢查這個鍵是否存在(在條件中),
另一次獲取這個鍵關聯的值,以便將其打印出來。
def describe_person(person):print('Description of',person['name'])print('Age:',person['age'])try:print('Occupation:',person['occupation'])except KeyError:pass
上面的這個函數直接假設存在’occupation’鍵。如果這種假設正確,就能省點事:直接獲取并打印值,而無需檢查這個鍵是否存在。如果這個鍵不存在,將引發KeyError異常,而except子句將捕獲這個異常。
檢查一個對象是否包含屬性write
try:obj.write
except AttributeError:print('The object is not writeable')
else:print('The object is writeable')
try子句只是訪問屬性write,而沒有使用它來做任何事情。
如果引發了AttributeError異常,說明對象沒有屬性write,否則就說明有這個屬性。
在很多情況下,相比于使用if/else,使用try/except語句更自然,也更符合Python的風格。
不那么異常的情況
如果你只想發出警告,指出情況偏離了正軌,可使用模塊warnings中的函數warn
。
警告只顯示一次。如果再次運行最后一行代碼,什么事情都不會發生。
from warnings import warn
warn('I like beyond band')
'''
D:\Anaconda3\lib\site-packages\ipykernel_launcher.py:2: UserWarning: I like beyond band
'''
如果其他代碼在使用你的模塊,可使用模塊warnings中的函數filterwarnings來抑制你發出的警告(或特定類型的警告),并指定要采取的措施,如"error"或"ignore"。
from warnings import filterwarnings
filterwarnings("ignore")
warn("Anyone out there?")
filterwarnings("error")
warn("Something is very wrong!")'''
UserWarning Traceback (most recent call last)
<ipython-input-13-2678cd9c1908> in <module>()3 warn("Anyone out there?")4 filterwarnings("error")
----> 5 warn("Something is very wrong!")UserWarning: Something is very wrong!
'''
上述引發的異常為UserWarning。發出警告時,可指定將引發的異常(即警告類別),但必須是Warning的子類。如果將警告轉換為錯誤,將使用你指定的異常。另外,還可根據異常來過濾掉特定類型的警告。
from warnings import filterwarnings
filterwarnings("error")
warn("This function is really old...",DeprecationWarning)'''
DeprecationWarning Traceback (most recent call last)
<ipython-input-14-db2d386b9ad9> in <module>()1 from warnings import filterwarnings2 filterwarnings("error")
----> 3 warn("This function is really old...",DeprecationWarning)DeprecationWarning: This function is really old...
'''
from warnings import filterwarnings
filterwarnings("ignore", category=DeprecationWarning)
warn("Another deprecation warning.", DeprecationWarning)
warn("Something else.")'''
UserWarning Traceback (most recent call last)
<ipython-input-15-2ae8758ff90f> in <module>()1 filterwarnings("ignore", category=DeprecationWarning)2 warn("Another deprecation warning.", DeprecationWarning)
----> 3 warn("Something else.")UserWarning: Something else.
'''
小結
概念 | 解釋 |
---|---|
異常對象 | 異常情況(如發生錯誤)是用異常對象表示的。對于異常情況,有多種處理方式;如果忽略,將導致程序終止。 |
引發異常 | 可使用raise語句來引發異常。它將一個異常類或異常實例作為參數,但你也可提供兩個參數(異常和錯誤消息)。如果在except子句中調用raise時沒有提供任何參數,它將重新引發該子句捕獲的異常。 |
自定義的異常類 | 你可通過從Exception派生來創建自定義的異常。 |
捕獲異常 | 要捕獲異常,可在try語句中使用except子句。在except子句中,如果沒有指定異常類,將捕獲所有的異常。你可指定多個異常類,方法是將它們放在元組中。如果向except提供兩個參數,第二個參數將關聯到異常對象。在同一條try/except語句中,可包含多個except子句,以便對不同的異常采取不同的措施。 |
else子句 | 除except子句外,你還可使用else子句,它在主try塊沒有引發異常時執行。 |
finally | 要確保代碼塊(如清理代碼)無論是否引發異常都將執行,可使用try/finally,并將代碼塊放在finally子句中。 |
異常和函數 | 在函數中引發異常時,異常將傳播到調用函數的地方(對方法來說亦如此)。 |
警告 | 警告類似于異常,但(通常)只打印一條錯誤消息。你可指定警告類別,它們是Warning的子類。 |
本章介紹的新函數
函數 | 描述 |
---|---|
warnings.filterwarnings(action,category=Warning, …) | 用于過濾警告 |
warnings.warn(message, category=None) | 用于發出警告 |