花下貓語:最近,我在看 Python 3.10 版本的更新內容時,發現有一個關於上下文管理器的小更新,然後,突然發現上下文管理器的設計 PEP 竟然還沒人翻譯過!於是,我斷斷續續花了兩週時間,終於把這篇 PEP 翻譯出來了。如果你不瞭解什麼是 PEP,可以先檢視這篇《學習Python,怎能不懂點PEP呢?》,如果你也對翻譯 PEP 感興趣,歡迎加入 Github 上的 peps-cn 專案。
PEP原文 : https://www.python.org/dev/peps/pep-0343
PEP標題: PEP 343 -- The "with" Statement
PEP作者: Guido van Rossum, Nick Coghlan
建立日期: 2005-05-13
合入版本: 2.5
譯者 :豌豆花下貓@Python貓公眾號
PEP翻譯計劃 :https://github.com/chinesehuazhou/peps-cn
摘要
本 PEP 提議在 Python 中新增一種"with"語句,可以取代常規的 try/finally 語句。
在本 PEP 中,上下文管理器提供__enter__() 和 __exit__() 方法,在進入和退出 with 語句體時,這倆方法分別會被呼叫。
作者的批註
本 PEP 最初由 Guido 以第一人稱編寫,隨後由 Nick Coghlan 根據 python-dev 上的討論,做出了更新補充。所有第一人稱的內容都出自於 Guido 的原文。
Python 的 alpha 版本釋出週期暴露了本 PEP 以及相關文件和實現[14]中的術語問題。直到 Python 2.5 的第一個 beta 版本釋出時,本 PEP 才穩定下來。
是的,本文某些地方的動詞時態是混亂的。到現在為止,我們已經創作此 PEP 一年多了,所以,有些原本在未來的事情,現在已經成為過去了:)
介紹
經過對 PEP-340 及其替代方案的大量討論後,我決定撤銷 PEP-340,並提出了 PEP-310 的一個小變種。經過更多的討論後,我又新增了一種機制,可以使用 throw() 方法,在掛起的生成器中丟擲異常,或者用一個 close() 方法丟擲一個 GeneratorExitexception;這些想法最初是在 python-dev [2] 上提出的,並得到了普遍的認可。我還將關鍵字改為了“with”。
(Python貓注:PEP-340 也是 Guido 寫的,他最初用的關鍵字是“block”,後來改成了其它 PEP 提議的“with”。)
在本 PEP 被接受後,以下 PEP 由於重疊而被拒絕:
- PEP-310,可靠的獲取/釋放對。這是 with 語句的原始提案。
- PEP-319,Python 同步/非同步程式碼塊。通過提供合適的 with 語句控制器,本 PEP 可以涵蓋它的使用場景:對於'synchronize',我們可以使用示例 1 中的"locking"模板;對於'asynchronize',我們可以使用類似的"unlock"模板。我認為不必要給程式碼塊加上“匿名的”鎖;事實上,應該儘可能地使用明確的互斥鎖。
PEP-340 和 PEP-346 也與本 PEP 重疊,但當本 PEP 被提交時,它們就自行撤銷了。
關於本 PEP 早期版本的一些討論,可以在 Python Wiki[3] 上檢視。
動機與摘要
PEP-340(即匿名的 block 語句)包含了許多強大的創意:使用生成器作為程式碼塊模板、給生成器新增異常處理和終結,等等。除了讚揚之外,它還被很多人所反對,他們不喜歡它是一個(潛在的)迴圈結構。這意味著塊語句中的 break 和 continue 可以中斷或繼續塊語句,即使它原本被當作非迴圈的資源管理工具。
但是,直到我讀了 Raymond Chen 對流量控制巨集[1]的抨擊時,PEP-340 才走入了末路。Raymond 令人信服地指出,在巨集中藏有流程控制會讓你的程式碼變得難以捉摸,我覺得他的論點不僅適用於 C,同樣適用於 Python。我意識到,PEP-340 的模板可以隱藏各種控制流;例如,它的示例 4 (auto_retry())捕獲了異常,並將程式碼塊重複三次。
然而,在我看來,PEP-310 的 with 語句並沒有隱藏控制流:雖然 finally 程式碼部分會暫時掛起控制流,但到了最後,控制流會恢復,就好像 finally 子句根本不存在一樣。
在 PEP-310 中,它大致提出了以下的語法("VAR ="部分是可選的):
with VAR = EXPR:
BLOCK
大致可以理解為:
VAR = EXPR
VAR.__enter__()
try:
BLOCK
finally:
VAR.__exit__()
現在考慮這個例子:
with f = open("/etc/passwd"):
BLOCK1
BLOCK2
在上例中,第一行就像是一個“if True”,我們知道如果 BLOCK1 在執行時沒有拋異常,那麼 BLOCK2 將會被執行;如果 BLOCK1 丟擲異常,或執行了非區域性的 goto (即 break、continue 或 return),那麼 BLOCK2 就不會被執行。也就是說,with 語句所加入的魔法並不會影響到這種流程邏輯。
(你可能會問,如果__exit__() 方法因為 bug 導致拋異常怎麼辦?那麼一切都完了——但這並不比其他情況更糟;異常的本質就是,它們可能發生在任何地方,你只能接受這一點。即便你寫的程式碼沒有 bug,KeyboardInterrupt 異常仍然會導致程式在任意兩個虛擬機器操作碼之間退出。)
這個論點幾乎讓我採納了 PEP-310,但是, PEP-340 還有一個亮點讓我不忍放棄:使用生成器作為某些抽象化行為的“模板”,例如獲取及釋放一個鎖,或者開啟及關閉一個檔案,這是一種很強大的想法,通過該 PEP 的例子就能看得出來。
受到 Phillip Eby 對 PEP-340 的反提議(counter-proposal)的啟發,我嘗試建立一個裝飾器,將合適的生成器轉換為具有必要的__enter__() 和 __exit__() 方法的物件。我在這裡遇到了一個障礙:雖然這對於鎖的例子來說並不太難,但是對於開啟檔案的例子,卻不可能做到這一點。我的想法是像這樣定義模板:
@contextmanager
def opening(filename):
f = open(filename)
try:
yield f
finally:
f.close()
並這樣使用它:
with f = opening(filename):
...read data from f...
問題是在 PEP-310 中,EXPR 的呼叫結果直接分配給 VAR,然後 VAR 的__exit__() 方法會在 BLOCK1 退出時被呼叫。但是這裡,VAR 顯然需要接收開啟的檔案,這意味著__exit__() 必須是檔案物件的一個方法。
雖然這可以使用代理類來解決,但會很彆扭,同時我還意識到,只需做出一個小小的轉變,就能輕輕鬆鬆地寫出所需的裝飾器:讓 VAR 接收__enter__() 方法的呼叫結果,接著儲存 EXPR 的值,以便最後呼叫它的__exit__() 方法。
然後,裝飾器可以返回一個包裝器的例項,其__enter__() 方法呼叫生成器的 next() 方法,並返回 next() 所返回的值;包裝器例項的__exit__() 方法再次呼叫 next(),但期望它丟擲 StopIteration。(詳細資訊見下文的生成器裝飾器部分。)
因此,最後一個障礙便是 PEP-310 語法:
with VAR = EXPR:
BLOCK1
這是有欺騙性的,因為 VAR 不接收 EXPR 的值。借用 PEP-340 的語法,很容易改成:
with EXPR as VAR:
BLOCK1
在其他的討論中,人們真的很喜歡能夠“看到”生成器中的異常,儘管僅僅是為了記日誌;生成器不允許產生(yield)其它的值,因為 with 語句不應該作為迴圈使用(引發不同的異常是勉強可以接受的)。
為了做到這點,我建議為生成器提供一個新的 throw() 方法,該方法以通常的方式接受 1 到 3 個引數(型別、值、回溯),表示一個異常,並在生成器掛起的地方丟擲。
一旦我們有了這個,下一步就是新增另一個生成器方法 close(),它用一個特殊的異常(即 GeneratorExit)呼叫 throw(),可以令生成器退出。有了這個,在生成器被當作垃圾回收時,可以讓程式自動呼叫 close()。
最後,我們可以允許在 try-finally 語句中使用 yield 語句,因為我們現在可以保證 finally 子句必定被執行。關於終結(finalization)的常見注意事項——程式可能會在沒有終結任何物件的情況下突然被終止,而這些物件可能會因程式的週期或記憶體洩漏而永遠存活(在 Python 的實現中,週期或記憶體洩漏會由 GC 妥善處理)。
請注意,在使用完生成器物件後,我們不保證會立即執行 finally 子句,儘管在 CPython 中是這樣實現的。這類似於自動關閉檔案:像 CPython 這樣的引用計數型直譯器,它會在最後一個引用消失時釋放一個物件,而使用其他 GC 演算法的直譯器不保證也是如此。這指的是 Jython、IronPython,可能包括執行在 Parrot 上的 Python。
(關於對生成器所做的更改,可以在 PEP-342 中找到細節,而不是在當前 PEP 中。)
用例
請參閱文件末尾的示例部分。
規格說明:'with'語句
提出了一種新的語句,語法如下:
with EXPR as VAR:
BLOCK
在這裡,“with”和“as”是新的關鍵字;EXPR 是任意一個表示式(但不是表示式列表),VAR 是一個單一的賦值目標。它不能是以逗號分隔的變數序列,但可以是以圓括號包裹的以逗號分隔的變數序列。(這個限制使得將來的語法擴充套件可以出現多個逗號分隔的資源,每個資源都有自己的可選 as 子句。)
“as VAR”部分是可選的。
上述語句可以被翻譯為:
mgr = (EXPR)
exit = type(mgr).__exit__ # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
try:
VAR = value # Only if "as VAR" is present
BLOCK
except:
# The exceptional case is handled here
exc = False
if not exit(mgr, *sys.exc_info()):
raise
# The exception is swallowed if exit() returns true
finally:
# The normal and non-local-goto cases are handled here
if exc:
exit(mgr, None, None, None)
在這裡,小寫變數(mgr、exit、value、exc)是內部變數,使用者不能訪問;它們很可能是由特殊的暫存器或堆疊位置來實現。
上述詳細的翻譯旨在說明確切的語義。直譯器會按照順序查詢相關的方法(__exit__、__enter__),如果沒有找到,將引發 AttributeError。類似地,如果任何一個呼叫引發了異常,其效果與上述程式碼中的效果完全相同。
最後,如果 BLOCK 包含 break、continue 或 return 語句,__exit__() 方法就會被呼叫,帶三個 None 引數,就跟 BLOCK 正常執行完成一樣。(也就是說,__exit__() 不會將這些“偽異常”視為異常。)
如果語法中的"as VAR"部分被省略了,則翻譯中的"VAR ="部分也要被忽略(但 mgr.__enter__() 仍然會被呼叫)。
mgr.__exit__() 的呼叫約定如下。如果 finally 子句是通過 BLOCK 的正常完成或通過非區域性 goto(即 BLOCK 中的 break、continue 或 return 語句)到達,則使用三個 None 引數呼叫mgr.__exit__()。如果 finally 子句是通過 BLOCK 引發的異常到達,則使用異常的型別、值和回溯這三個引數呼叫 mgr.__exit__()。
重要:如果 mgr.__exit__() 返回“true”,則異常將被“吞滅”。也就是說,如果返回"true",即便在 with 語句內部發生了異常,也會繼續執行 with 語句之後的下一條語句。然而,如果 with 語句通過非區域性 goto (break、continue 或 return)跳出,則這個非區域性返回將被重置,不管 mgr.__exit__() 的返回值是什麼。這個細節的動機是使 mgr.__exit__() 能夠吞嚥異常,而不使異常產生影響(因為預設的返回值 None為 false,這會導致異常被重新 raise)。吞下異常的主要用途是使編寫 @contextmanager 裝飾器成為可能,這樣被裝飾的生成器中的 try/except 程式碼塊的行為就好像生成器的主體在 with-語句裡內聯展開了一樣。
之所以將異常的細節傳給__exit__(),而不用 PEP -310 中不帶引數的__exit__(),原因是考慮到下面例子 3 的 transactional()。該示例會根據是否發生異常,從而決定提交或回滾事務。我們沒有用一個 bool 標誌區分是否發生異常,而是傳了完整的異常資訊,目的是可以記錄異常日誌。依賴於 sys.exc_info() 獲取異常資訊的提議被拒絕了;因為 sys.exc_info() 有著非常複雜的語義,它返回的異常資訊完全有可能是很久之前就捕獲的。有人還提議新增一個布林值,用於區分是到達 BLOCK 結尾,還是非區域性 goto。這因為過於複雜和不必要而被拒絕;對於資料庫事務回滾,非區域性 goto 應該被認為是正常的。
為了促進 Python 程式碼中上下文的連結作用,__exit__() 方法不應該繼續 raise 傳遞給它的錯誤。在這種情況下,__exit__() 方法的呼叫者應該負責處理 raise。
這樣,如果呼叫者想知道__exit__() 是否呼叫失敗(而不是在傳出原始錯誤之前就完成清理),它就可以自己判斷。
如果__exit__() 沒有返回錯誤,那麼就可以將__exit__() 方法本身解釋為成功(不管原始錯誤是被傳播還是抑制)。
然而,如果__exit__() 向其呼叫者傳播了異常,這就意味著__exit__() 本身已經失敗。因此,__exit__() 方法應該避免引發錯誤,除非它們確實失敗了。(允許原始錯誤繼續並不是失敗。)
過渡計劃
在 Python 2.5 中,新語法需要通過 future 引入:
from __future__ import with_statement
它會引入'with'和'as'關鍵字。如果沒有匯入,使用'with'或'as'作為識別符號時,將導致報錯。
在 Python 2.6 中,新語法總是生效的,'with'和'as'已經是關鍵字。
生成器裝飾器
隨著 PEP-342 被採納,我們可以編寫一個裝飾器,令其使用只 yield 一次的生成器來控制 with 語句。這是一個裝飾器的粗略示例:
class GeneratorContextManager(object):
def __init__(self, gen):
self.gen = gen
def __enter__(self):
try:
return self.gen.next()
except StopIteration:
raise RuntimeError("generator didn't yield")
def __exit__(self, type, value, traceback):
if type is None:
try:
self.gen.next()
except StopIteration:
return
else:
raise RuntimeError("generator didn't stop")
else:
try:
self.gen.throw(type, value, traceback)
raise RuntimeError("generator didn't stop after throw()")
except StopIteration:
return True
except:
# only re-raise if it's *not* the exception that was
# passed to throw(), because __exit__() must not raise
# an exception unless __exit__() itself failed. But
# throw() has to raise the exception to signal
# propagation, so this fixes the impedance mismatch
# between the throw() protocol and the __exit__()
# protocol.
#
if sys.exc_info()[1] is not value:
raise
def contextmanager(func):
def helper(*args, **kwds):
return GeneratorContextManager(func(*args, **kwds))
return helper
這個裝飾器可以這樣使用:
@contextmanager
def opening(filename):
f = open(filename) # IOError is untouched by GeneratorContext
try:
yield f
finally:
f.close() # Ditto for errors here (however unlikely)
這個裝飾器的健壯版本將會加入到標準庫中。
標準庫中的上下文管理器
可以將__enter__() 和__exit__() 方法賦予某些物件,如檔案、套接字和鎖,這樣就不用寫:
with locking(myLock):
BLOCK
而是簡單地寫成:
with myLock:
BLOCK
我想我們應該謹慎對待它;它可能會導致以下的錯誤:
f = open(filename)
with f:
BLOCK1
with f:
BLOCK2
它可能跟你想的不一樣(在進入 block2 之前,f 已經關閉了)。
另一方面,這樣的錯誤很容易診斷;例如,當第二個 with 語句再呼叫 f.__enter__() 時,上面的生成器裝飾器將引發 RuntimeError。如果在一個已關閉的檔案物件上呼叫__enter__,則可能引發類似的錯誤。
在 Python 2.5中,以下型別被標識為上下文管理器:
- file
- thread.LockType
- threading.Lock
- threading.RLock
- threading.Condition
- threading.Semaphore
- threading.BoundedSemaphore
還將在 decimal 模組新增一個上下文管理器,以支援在 with 語句中使用本地的十進位制算術上下文,並在退出 with 語句時,自動恢復原始上下文。
標準術語
本 PEP 提議將由__enter__() 和 __exit__() 方法組成的協議稱為“上下文管理器協議”,並將實現該協議的物件稱為“上下文管理器”。[4]
緊跟著 with 關鍵字的表示式被稱為“上下文表示式”,該表示式提供了上下文管理器在with 程式碼塊中所建立的執行時環境的主要線索。
目前為止, with 語句體中的程式碼和 as 關鍵字後面的變數名(一個或多個)還沒有特殊的術語。可以使用一般的術語“語句體”和“目標列表”,如果這些術語不清晰,可以使用“with”或“with statement”作為字首。
考慮到可能存在 decimal 模組的算術上下文這樣的物件,因此術語“上下文”是有歧義的。如果想要更加具體的話,可以使用術語“上下文管理器”,表示上下文表示式所建立的具體物件;使用術語“執行時上下文”或者(最好是)"執行時環境",表示上下文管理器所做出的實際狀態的變更。當簡單地討論 with 語句的用法時,歧義性無關緊要,因為上下文表示式完全定義了對執行時環境所做的更改。當討論 with 語句本身的機制以及如何實際實現上下文管理器時,這些術語的區別才是重要的。
快取上下文管理器
許多上下文管理器(例如檔案和基於生成器的上下文)都是一次性的物件。一旦__exit__() 方法被呼叫,上下文管理器將不再可用(例如:檔案已經被關閉,或者底層生成器已經完成執行)。
對於多執行緒程式碼,以及巢狀的 with 語句想要使用同一個上下文管理器,最簡單的方法是給每個 with 語句一個新的管理器物件。並非巧合的是,標準庫中所有支援重用的上下文管理器都來自 threading 模組——它們都被設計用來處理由執行緒和巢狀使用所產生的問題。
這意味著,為了儲存帶有特定初始化引數(為了用在多個 with 語句)的上下文管理器,通常需要將它儲存在一個無引數的可呼叫物件,然後在每個語句的上下文表示式中呼叫,而不是直接把上下文管理器快取起來。
如果此限制不適用,在受影響的上下文管理器的文件中,應該清楚地指出這一點。
解決的問題
以下的問題經由 BDFL 的裁決而解決(並且在 python-dev 上沒有重大的反對意見)。
1、當底層的生成器-迭代器行為異常時,GeneratorContextManager 應該引發什麼異常?下面引用的內容是 Guido 為本 PEP及 PEP-342 (見[8])中生成器的 close() 方法選擇 RuntimeError 的原因:“我不願意只是為了它而引入一個新的異常類,因為這不是我想讓人們捕獲的異常:我想讓它變成一個回溯(traceback),被程式設計師看到並且修復。因此,我認為它們都應該引發 RuntimeError。有一些引發 RuntimeError 的先例:Python 核心程式碼在檢測到無限遞迴時,遇到未初始化的物件時(以及其它各種各樣的情況)。”
2、如果在with語句所涉及的類中沒有相關的方法,則最好是丟擲AttributeError而不是TypeError。抽象物件C API引發TypeError而不是AttributeError,這只是歷史的一個偶然,而不是經過深思熟慮的設計決策[11]。
3、帶有__enter__ /__exit__方法的物件被稱為“上下文管理器”,將生成器函式轉化為上下文管理器工廠的是 contextlib.contextmanager 裝飾器。在 2.5版本釋出期間,有人提議使用其它的叫法[16],但沒有足夠令人信服的理由。
拒絕的選項
在長達幾個月的時間裡,對於是否要抑制異常(從而避免隱藏的流程控制),出現了一場令人痛苦的拉鋸戰,最終,Guido 決定要抑制異常[13]。
本 PEP 的另一個話題也引起了無休止的爭論,即是否要提供一個__context__() 方法,類似於可迭代物件的__iter__() 方法[5][7][9]。源源不斷的問題[10][13]在解釋它是什麼、為什麼是那樣、以及它是如何工作的,最終導致 Guido 完全拋棄了這個東西[15](這很讓人歡欣鼓舞!)
還有人提議直接使用 PEP-342 的生成器 API 來定義 with 語句[6],但這很快就不予考慮了,因為它會導致難以編寫不基於生成器的上下文管理器。
例子
基於生成器的示例依賴於 PEP-342。另外,有些例子是不實用的,因為標準庫中有現成的物件可以在 with 語句中直接使用,例如 threading.RLock。
例子中那些函式名所用的時態並不是隨意的。過去時態(“-ed”)的函式指的是在__enter__方法中執行,並在__exit__方法中反執行的動作。進行時態("-ing")的函式指的是準備在__exit__方法中執行的動作。
1、一個鎖的模板,在開始時獲取,在離開時釋放:
@contextmanager
def locked(lock):
lock.acquire()
try:
yield
finally:
lock.release()
使用如下:
with locked(myLock):
# Code here executes with myLock held. The lock is
# guaranteed to be released when the block is left (even
# if via return or by an uncaught exception).
2、一個開啟檔案的模板,確保當程式碼被執行後,檔案會被關閉:
@contextmanager
def opened(filename, mode="r"):
f = open(filename, mode)
try:
yield f
finally:
f.close()
使用如下:
with opened("/etc/passwd") as f:
for line in f:
print line.rstrip()
3、一個資料庫事務的模板,用於提交或回滾:
@contextmanager
def transaction(db):
db.begin()
try:
yield None
except:
db.rollback()
raise
else:
db.commit()
4、不使用生成器,重寫例子 1:
class locked:
def __init__(self, lock):
self.lock = lock
def __enter__(self):
self.lock.acquire()
def __exit__(self, type, value, tb):
self.lock.release()
(這個例子很容易被修改來實現其他相對無狀態的例子;這表明,如果不需要保留特殊的狀態,就不必要使用生成器。)
5、臨時重定向 stdout:
@contextmanager
def stdout_redirected(new_stdout):
save_stdout = sys.stdout
sys.stdout = new_stdout
try:
yield None
finally:
sys.stdout = save_stdout
使用如下:
with opened(filename, "w") as f:
with stdout_redirected(f):
print "Hello world"
當然,這不是執行緒安全的,但是若不用管理器的話,本身也不是執行緒安全的。在單執行緒程式(例如指令碼)中,這種做法很受歡迎。
6、opened() 的一個變體,也返回一個錯誤條件:
@contextmanager
def opened_w_error(filename, mode="r"):
try:
f = open(filename, mode)
except IOError, err:
yield None, err
else:
try:
yield f, None
finally:
f.close()
使用如下:
with opened_w_error("/etc/passwd", "a") as (f, err):
if err:
print "IOError:", err
else:
f.write("guido::0:0::/:/bin/sh\n")
7、另一個有用的操作是阻塞訊號。它的用法是這樣的:
import signal
with signal.blocked():
# code executed without worrying about signals
它的引數是可選的,表示要阻塞的訊號列表;在預設情況下,所有訊號都被阻塞。具體實現就留給讀者作為練習吧。
8、此特性還有一個用途是 Decimal 上下文。下面是 Michael Chermside 釋出的一個簡單的例子:
import decimal
@contextmanager
def extra_precision(places=2):
c = decimal.getcontext()
saved_prec = c.prec
c.prec += places
try:
yield None
finally:
c.prec = saved_prec
示例用法(摘自 Python 庫參考文件):
def sin(x):
"Return the sine of x as measured in radians."
with extra_precision():
i, lasts, s, fact, num, sign = 1, 0, x, 1, x, 1
while s != lasts:
lasts = s
i += 2
fact *= i * (i-1)
num *= x * x
sign *= -1
s += num / fact * sign
# The "+s" rounds back to the original precision,
# so this must be outside the with-statement:
return +s
9、下面是 decimal 模組的一個簡單的上下文管理器:
@contextmanager
def localcontext(ctx=None):
"""Set a new local decimal context for the block"""
# Default to using the current context
if ctx is None:
ctx = getcontext()
# We set the thread context to a copy of this context
# to ensure that changes within the block are kept
# local to the block.
newctx = ctx.copy()
oldctx = decimal.getcontext()
decimal.setcontext(newctx)
try:
yield newctx
finally:
# Always restore the original context
decimal.setcontext(oldctx)
示例用法:
from decimal import localcontext, ExtendedContext
def sin(x):
with localcontext() as ctx:
ctx.prec += 2
# Rest of sin calculation algorithm
# uses a precision 2 greater than normal
return +s # Convert result to normal precision
def sin(x):
with localcontext(ExtendedContext):
# Rest of sin calculation algorithm
# uses the Extended Context from the
# General Decimal Arithmetic Specification
return +s # Convert result to normal context
10、一個通用的“物件關閉”上下文管理器:
class closing(object):
def __init__(self, obj):
self.obj = obj
def __enter__(self):
return self.obj
def __exit__(self, *exc_info):
try:
close_it = self.obj.close
except AttributeError:
pass
else:
close_it()
這可以確保關閉任何帶有 close 方法的東西,無論是檔案、生成器,還是其他東西。它甚至可以在物件並不需要關閉的情況下使用(例如,一個接受了任意可迭代物件的函式):
# emulate opening():
with closing(open("argument.txt")) as contradiction:
for line in contradiction:
print line
# deterministically finalize an iterator:
with closing(iter(data_source)) as data:
for datum in data:
process(datum)
(Python 2.5 的 contextlib 模組包含了這個上下文管理器的一個版本)
11、PEP-319 給出了一個用例,它也有一個 release() 上下文,能臨時釋放先前獲得的鎖;這個用例跟前文的例子 4 很相似,只是交換了 acquire() 和 release() 的呼叫:
class released:
def __init__(self, lock):
self.lock = lock
def __enter__(self):
self.lock.release()
def __exit__(self, type, value, tb):
self.lock.acquire()
示例用法:
with my_lock:
# Operations with the lock held
with released(my_lock):
# Operations without the lock
# e.g. blocking I/O
# Lock is held again here
12、一個“巢狀型”上下文管理器,自動從左到右巢狀所提供的上下文,可以避免過度縮排:
@contextmanager
def nested(*contexts):
exits = []
vars = []
try:
try:
for context in contexts:
exit = context.__exit__
enter = context.__enter__
vars.append(enter())
exits.append(exit)
yield vars
except:
exc = sys.exc_info()
else:
exc = (None, None, None)
finally:
while exits:
exit = exits.pop()
try:
exit(*exc)
except:
exc = sys.exc_info()
else:
exc = (None, None, None)
if exc != (None, None, None):
# sys.exc_info() may have been
# changed by one of the exit methods
# so provide explicit exception info
raise exc[0], exc[1], exc[2]
示例用法:
with nested(a, b, c) as (x, y, z):
# Perform operation
等價於:
with a as x:
with b as y:
with c as z:
# Perform operation
(Python 2.5 的 contextlib 模組包含了這個上下文管理器的一個版本)
參考實現
在 2005 年 6 月 27 日的 EuroPython 會議上,Guido 首次採納了這個 PEP。之後它新增了__context__方法,並被再次採納。此 PEP 在 Python 2.5 a1 子版本中實現,__context__() 方法在 Python 2.5b1 中被刪除。
致謝
許多人對這個 PEP 中的想法和概念作出了貢獻,包括在 PEP-340 和 PEP-346 的致謝中提到的所有人。
另外,還要感謝(排名不分先後):Paul Moore, Phillip J. Eby, Greg Ewing, Jason Orendorff, Michael Hudson, Raymond Hettinger, Walter Dörwald, Aahz, Georg Brandl, Terry Reedy, A.M. Kuchling, Brett Cannon,以及所有參與了 python-dev 討論的人。
參考連結
[1] Raymond Chen's article on hidden flow controlhttps://devblogs.microsoft.com/oldnewthing/20050106-00/?p=36783
[2] Guido suggests some generator changes that ended up in PEP 342https://mail.python.org/pipermail/python-dev/2005-May/053885.html
[3] Wiki discussion of PEP 343http://wiki.python.org/moin/WithStatement
[4] Early draft of some documentation for the with statementhttps://mail.python.org/pipermail/python-dev/2005-July/054658.html
[5] Proposal to add the with methodhttps://mail.python.org/pipermail/python-dev/2005-October/056947.html
[6] Proposal to use the PEP 342 enhanced generator API directlyhttps://mail.python.org/pipermail/python-dev/2005-October/056969.html
[7] Guido lets me (Nick Coghlan) talk him into a bad idea ?https://mail.python.org/pipermail/python-dev/2005-October/057018.html
[8] Guido raises some exception handling questionshttps://mail.python.org/pipermail/python-dev/2005-June/054064.html
[9] Guido answers some questions about the context methodhttps://mail.python.org/pipermail/python-dev/2005-October/057520.html
[10] Guido answers more questions about the context methodhttps://mail.python.org/pipermail/python-dev/2005-October/057535.html
[11] Guido says AttributeError is fine for missing special methodshttps://mail.python.org/pipermail/python-dev/2005-October/057625.html
[12] Original PEP 342 implementation patchhttp://sourceforge.net/tracker/index.php?func=detail&aid=1223381&group_id=5470&atid=305470
[13] (1, 2) Guido restores the ability to suppress exceptionshttps://mail.python.org/pipermail/python-dev/2006-February/061909.html
[14] A simple question kickstarts a thorough review of PEP 343https://mail.python.org/pipermail/python-dev/2006-April/063859.html
[15] Guido kills the context() methodhttps://mail.python.org/pipermail/python-dev/2006-April/064632.html
[16] Proposal to use 'context guard' instead of 'context manager'https://mail.python.org/pipermail/python-dev/2006-May/064676.html
版權
本文件已進入公共領域。