Python高階程式設計之上下文管理器
上下文管理器
上下文管理器與裝飾器類似,它們都是包裝其他程式碼的工具。但裝飾器用於包裝定義的程式碼塊(如函式或類),而上下文管理器可以包裝任意格式的程式碼塊。 如果進入上下文管理器就一定會有退出步驟,因此上下文管理器應用最多的地方就是作為資源被正確清理的一種方式。 舉個簡單的例子,在python中對一個txt檔案進行寫入操作,寫入後需要關閉檔案
try:
f = open('test.txt', 'r') # 開啟一個檔案
f.write('hello')
finally:
f.close() # 關閉該檔案
這樣的寫法雖然沒有問題,但總是需要手動關閉檔案,很容易引起漏洞,並且在經常需要對檔案進行讀寫的程式碼中顯得並不pythonic。 python的內建函式open
with open('test.txt', 'r') as f:
f.write('hello')
with語句的作用實際上就是對其後的程式碼求值(本例中即為呼叫open函式)。該表示式返回一個物件,該物件包含兩個特殊方法:__enter__
和__exit__
,__enter__
方法返回的結果會賦給as關鍵字後面的變數。這裡要注意,with後的表示式的結果沒有賦給所謂的變數,實際上返回值沒有賦給任何物件,只有__enter__
的返回值會被賦給該變數。
以下為一個簡單的示例:
class ContextManager(object):
def __init__(self):
self.entered = '未使用上下文管理器'
def __enter__(self):
self.entered = '進入上下文管理器'
return self
def __exit__(self, exc_type, exc_instance, traceback):
self.entered = '退出上下文管理器'
在python shell中執行以下操作
>> > cm = ContextManager() # 這裡只建立一個ContextManager的例項
>>> cm.entered # 檢視例項的entered值
'未使用上下文管理器'
# 如果使用ContextManager為上下文管理器
>>> with ContextManager() as cm:
cm.entered
'進入上下文管理器'
>>> cm.entered
'退出上下文管理器'
可以看出,with語句是進入上下文管理器的入口且預設執行__enter__
函式,而退出上下文管理器時預設執行__exit__
函式。
使用上下文管理器連線資料庫
開啟和關閉資源(如檔案和資料庫連線)是編寫上下文管理器的一個重要應用。確保出現異常時正確關閉資源往往很重要,這樣能夠避免最終隨著時間的推移而產生很多的殭屍程序。
上下文管理器的優勢在於此。通過在__enter__
方法中開啟資源並返回它,可以保證__exit__
方法能執行,同時也能對異常進行處理。
下面是一個連線MySql資料庫的上下文管理器:
import pymysql
class DBConnection(object):
def __init__(self,dbname=None, user=None, password=None, host='localhost'):
self.dbname = dbname
self.user = user
self.password = password
self.host = host
def __enter__(self):
self.conn = pymysql.connect(
host = self.host,
user = self.user,
password = self.password,
db = self.dbname)
return self.conn.cursor()
def __exit__(self, exc_type, exc_instance, traceback):
self.conn.commit()
self.conn.close()
"""
執行程式並在shell中使用這個上下文管理器即可進行資料庫操作
with DBConnection(user='root', dbname='資料庫名') as conn:
sql = 'SQL語句'
conn.execute(sql)
conn.fetchall()
"""
異常處理
with語句的表示式的作用是返回一個遵循特定協議的物件,該物件必須定義一個 __enter__
方法和一個__exit__
方法。
除了self引數,__enter__
方法不接受任何引數,如果有as變數(as子句是可選項 ),返回值賦給as後的變數。
除了self引數,__exit__
方法還帶有三個位置引數:
- 一個異常型別( exc_type )
- 一個異常例項( exc_instance )
- 一個回溯( traceback )
無異常時它們全為None
,但如果在程式碼塊內有異常發生,則引數被填充
上下文管理器必須定義__exit__
方法,該方法可以選擇性地處理包裝程式碼塊中出現的異常,或者處理其他需要關閉上下文管理器狀態的事情。如果__exit__
方法接收一個異常,它可以
- 返回False實現異常的傳播
- 返回True終止異常
- 丟擲一個不同的異常,它將替代異常被髮送出去
如下面這個類只處理特定異常的類
# 處理特定的異常類
class HandleValueError(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_instance, traceback):
if not exc_type: # 如果型別異常返回True
return True
if issubclass(exc_type,ValueError): # 如果異常型別為ValueError的子類則處理異常
print('Handling ValueError: %s.' % exc_instance)
return True
return False # 傳播任何其他異常
或者是定義一個只捕獲特定異常的類,但不希望顯示捕獲它的子類
# 不包括子類,例如想要捕獲一個給定的異常類,但不希望顯式地捕獲它的子類,
# 在傳統的except程式碼塊中不能這樣做(也不該這樣做)
class ValueErrorSubclass(ValueError):
pass
# 使用這個上下文管理器只會處理ValueError,而不會處理它的子類ValueErrorSubclass
class HandleValueError(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_instance, traceback):
if exc_type == ValueError:
print('Handling ValueError: %s.' % exc_instance)
return True
return False
上下文管理器提供了確保資源被正確處理的優秀方式,並且能夠將需要在程式中多個不同位置重複使用的異常程式碼封裝到一個位置。 與裝飾器一樣,上下文管理器適用於採納“只做一次”原則的工具,除非迫不得已需要在多處重複程式碼。裝飾器用於封裝命名函式與類,而上下文管理器更適用於封裝任意程式碼段。