1. 程式人生 > >python基礎——第8章 異常

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) 用於發出警告