python基礎——第8章 異常
異常)
- 每個異常都是某個類(比如:除零異常是ZeroDivisionError)的例項。
8.2 讓事情沿著你指定的軌道出錯
8.2.1 raise語句
要引發異常,可使用raise語句,
下面的示例使用的是內建異常類Exception:
>>> raise Exception
>Traceback (most recent call last):
File "<stdin>", line 1, in ?
Exception
>>> raise Exception('hyperdrive overload')
Traceback (most recent call last):
File "<stdin>", line 1 , in ?
Exception: hyperdrive overload
在第一個示例(raise Exception)中,引發的是通用異常,沒有指出出現了什麼錯誤。在第二個示例中,添加了錯誤訊息hyperdrive overload。 有很多內建的異常類,表8-1描述了最重要的幾個。在“Python庫參考手冊”的Built-in Exceptions一節,可找到有關所有內建異常類的描述。
表8-1 一些內建的異常類
類 名 | 描 述 |
---|---|
Exception | 幾乎所有的異常類都是從它派生而來的 |
AttributeError | 引用屬性或給它賦值失敗時引發 |
OSError | 作業系統不能執行指定的任務(如開啟檔案)時引發,有多個子類 |
IndexError | 使用序列中不存在的索引時引發,為LookupError的子類 |
KeyError | 使用對映中不存在的鍵時引發,為LookupError的子類 |
NameError | 找不到名稱(變數)時引發 |
SyntaxError | 程式碼不正確時引發 |
TypeError | 將內建操作或函式用於型別不正確的物件時引發 |
ValueError | 將內建操作或函式用於這樣的物件時引發:其型別正確但包含的值不合適 |
ZeroDivisionError | 在除法或求模運算的第二個引數為零時引發 |
8.2.2 自定義的異常類
那麼如何建立異常類呢?就像建立其他類一樣,但務必直接或間接地繼承Exception(這意味著從任何內建異常類派生都可以)。因此,自定義異常類的程式碼類似於下面這樣:
class SomeCustomException(Exception): pass
8.3 捕獲異常
為捕獲異常並對錯誤進行處理,可像下面這樣重寫這個程式:
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!")
注意 異常從函式向外傳播到呼叫函式的地方。如果在這裡也沒有被捕獲,異常將向程式的最頂層傳播。這意味著你可使用try/except來捕獲他人所編寫函式引發的異常。
8.3.1 不用提供引數
捕獲異常後,如果要重新引發它(即繼續向上傳播),可呼叫raise且不提供任何引數
為說明這很有用,來看一個能夠“抑制”異常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:
raise
如果無法處理異常,在except子句中使用不帶引數的raise通常是不錯的選擇,但有時你可能想引發別的異常。在這種情況下,導致進入except子句的異常將被作為異常上下文儲存起來,並出現在最終的錯誤訊息中,
在大多數情況下,更好的選擇是使用except Exception as e並對異常物件進行檢查。
# 通過使用else子句,可實現8.3.3節所說的迴圈。
while True:
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
value = x / y
print('x / y is', value)
except:
print('Invalid input. Please try again.')
else:
break
在這裡,僅當沒有引發異常時,才會跳出迴圈(這是由else子句中的break語句實現的)。換而言之,只要出現錯誤,程式就會要求使用者提供新的輸入。
8.3.7 最後
最後,還有finally子句,可用於在發生異常時執行清理工作。這個子句是與try子句配套的。
x = None
try:
x = 1 / 0
finally:
print('Cleaning up ...')
del x
在上述示例中,不管try子句中發生什麼異常,都將執行finally子句。為何在try子句之前初始化x呢?因為如果不這樣做,ZeroDivisionError將導致根本沒有機會給它賦值,進而導致在finally子句中對其執行del時引發未捕獲的異常。
如果執行這個程式,它將在執行清理工作後崩潰。
Cleaning up ...
Traceback (most recent call last):
File "C:\python\div.py", line 4, in ?
x = 1 / 0
ZeroDivisionError: integer division or modulo by zero
8.5 異常之禪
假設有一個字典,你要在指定的鍵存在時列印與之相關聯的值,否則什麼都不做。實現這種功能的程式碼可能類似於下面這樣:
def describe_person(person):
print('Description of', person['name'])
print('Age:', person['age'])
if 'occupation' in person:
print('Occupation:', person['occupation'])
如果你呼叫這個函式,並向它提供一個包含姓名Throatwobbler Mangrove和年齡42(但不包含職業)的字典,輸出將如下:
Description of Throatwobbler Mangrove
Age: 42
如果你在這個字典中新增職業camper,輸出將如下:
Description of Throatwobbler Mangrove
Age: 42
Occupation: camper
這段程式碼很直觀,但效率不高(雖然這裡的重點是程式碼簡潔),因為它必須兩次查詢’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子句將捕獲這個異常。
你可能發現,檢查物件是否包含特定的屬性時,try/except也很有用。例如,假設你要檢查一個物件是否包含屬性write,可使用類似於下面的程式碼:
try:
obj.write
except AttributeError:
print('The object is not writeable')
else:
print('The object is writeable')
在這裡,try子句只是訪問屬性write,而沒有使用它來做任何事情。如果引發了AttributeError異常,說明物件沒有屬性write,否則就說明有這個屬性。這種解決方案可替代7.2.9節介紹的使用getattr的解決方案,而且更自然。具體使用哪種解決方案,在很大程度上取決於個人喜好。
請注意,這裡在效率方面的提高並不大(實際上是微乎其微)。一般而言,除非程式存在效能方面的問題,否則不應過多考慮這樣的優化。關鍵是在很多情況下,相比於使用if/else,使用try/except語句更自然,也更符合Python的風格。因此你應養成儘可能使用try/except語句的習慣 ① 。
8.7 本章介紹的新函式
函 數 | 描 述 |
---|---|
warnings.filterwarnings(action,category=Warning, …) | 用於過濾警告 |
warnings.warn(message, category=None) | 用於發出警告 |