問題
你想讓你的物件支援上下文管理協議(with語句)。
解決方案
為了讓乙個物件相容 with 語句,你需要實現enter() 和exit() 方法。 例如,考慮如下的乙個類,它能為我們建立乙個網路連線:
from socket import socket, af_inet, sock_stream
class lazyconnection:
def __init__(self, address, family=af_inet, type=sock_stream):
self.address = address
self.family = family
self.type = type
self.sock = none
def __enter__(self):
if self.sock is not none:
raise runtimeerror('already connected')
self.sock = socket(self.family, self.type)
self.sock.connect(self.address)
return self.sock
def __exit__(self, exc_ty, exc_val, tb):
self.sock.close()
self.sock = none
這個類的關鍵特點在於它表示了乙個網路連線,但是初始化的時候並不會做任何事情(比如它並沒有建立乙個連線)。 連線的建立和關閉是使用 with 語句自動完成的,例如:
# conn.__exit__() executes: connection closed討論
編寫上下文管理器的主要原理是你的**會放到 with 語句塊中執行。 當出現 with 語句的時候,物件的enter() 方法被觸發, 它返回的值(如果有的話)會被賦值給 as 宣告的變數。然後,with 語句塊裡面的**開始執行。 最後,exit() 方法被觸發進行清理工作。
不管 with **塊中發生什麼,上面的控制流都會執行完,就算**塊中發生了異常也是一樣的。 事實上,exit() 方法的第三個引數包含了異常型別、異常值和追溯資訊(如果有的話)。exit() 方法能自己決定怎樣利用這個異常資訊,或者忽略它並返回乙個none值。 如果exit() 返回 true ,那麼異常會被清空,就好像什麼都沒發生一樣, with 語句後面的程式繼續在正常執行。
還有乙個細節問題就是 lazyconnection 類是否允許多個 with 語句來巢狀使用連線。 很顯然,上面的定義中一次只能允許乙個socket連線,如果正在使用乙個socket的時候又重複使用 with 語句, 就會產生乙個異常了。不過你可以像下面這樣修改下上面的實現來解決這個問題:
from socket import socket, af_inet, sock_stream
class lazyconnection:
def __init__(self, address, family=af_inet, type=sock_stream):
self.address = address
self.family = family
self.type = type
self.connections =
def __enter__(self):
sock = socket(self.family, self.type)
sock.connect(self.address)
return sock
def __exit__(self, exc_ty, exc_val, tb):
self.connections.pop().close()
# example use
from functools import partial
conn = lazyconnection(('www.python.org', 80))
with conn as s1:
pass
with conn as s2:
pass
# s1 and s2 are independent sockets
在第二個版本中,lazyconnection 類可以被看做是某個連線工廠。在內部,乙個列表被用來構造乙個棧。 每次enter() 方法執行的時候,它複製建立乙個新的連線並將其加入到棧裡面。exit() 方法簡單的從棧中彈出最後乙個連線並關閉它。 這裡稍微有點難理解,不過它能允許巢狀使用 with 語句建立多個連線,就如上面演示的那樣。
在需要管理一些資源比如檔案、網路連線和鎖的程式設計環境中,使用上下文管理器是很普遍的。 這些資源的乙個主要特徵是它們必須被手動的關閉或釋放來確保程式的正確執行。 例如,如果你請求了乙個鎖,那麼你必須確保之後釋放了它,否則就可能產生死鎖。 通過實現enter() 和exit() 方法並使用 with 語句可以很容易的避免這些問題, 因為exit() 方法可以讓你無需擔心這些了。
在contextmanager
模組中有乙個標準的上下文管理方案模板,可參考9.22小節。 同時在12.6小節中還有乙個對本節示例程式的執行緒安全的修改版。
32 讓物件支援上下文管理
例如,實現了乙個telnet客戶端的類telnetclient,呼叫例項的connect login interact 方法啟動客戶端與伺服器互動,互動完畢後呼叫cleanup 方法關閉已連線的socket,以及將操作歷史記錄寫入檔案並關閉。要求 讓telnetclient的例項支援上下文管理協議,...
Python 物件上下文管理協議
class myopen def init self,filename self.filename filename def enter self self.file open self.filename,r data self.file.read print data 如果 with 塊裡面的程式...
上下文管理協議
class open def init self,name self.name name def enter self print 執行enter def exit self,exc type,exc val,exc tb print 執行exit with open a.txt as f with...