contextlib
— with
語句上下文的實用工具¶
原始碼: Lib/contextlib.py
該模組為涉及 with
語句的常見任務提供了實用工具。有關更多資訊,另請參閱 上下文管理器型別 和 With 語句上下文管理器。
實用工具¶
提供的函式和類
- class contextlib.AbstractContextManager¶
實現
object.__enter__()
和object.__exit__()
的類的 抽象基類。object.__enter__()
提供了返回self
的預設實現,而object.__exit__()
是一個抽象方法,預設返回None
。另請參閱 上下文管理器型別 的定義。3.6 版本新增。
- class contextlib.AbstractAsyncContextManager¶
實現
object.__aenter__()
和object.__aexit__()
的類的 抽象基類。object.__aenter__()
提供了返回self
的預設實現,而object.__aexit__()
是一個抽象方法,預設返回None
。另請參閱 非同步上下文管理器 的定義。3.7 版本新增。
- @contextlib.contextmanager¶
此函式是一個 裝飾器,可用於定義
with
語句上下文管理器的工廠函式,而無需建立類或單獨的__enter__()
和__exit__()
方法。雖然許多物件本身就支援在 with 語句中使用,但有時需要管理一個資源,它本身不是上下文管理器,並且沒有實現
close()
方法以與contextlib.closing
一起使用。一個抽象的例子如下,以確保正確的資源管理
from contextlib import contextmanager @contextmanager def managed_resource(*args, **kwds): # Code to acquire resource, e.g.: resource = acquire_resource(*args, **kwds) try: yield resource finally: # Code to release resource, e.g.: release_resource(resource)
然後可以像這樣使用該函式
>>> with managed_resource(timeout=3600) as resource: ... # Resource is released at the end of this block, ... # even if code in the block raises an exception
被裝飾的函式在被呼叫時必須返回一個 生成器-迭代器。此迭代器必須只產生一個值,該值將繫結到
with
語句的as
子句中的目標(如果有)。在生成器產生值的位置,執行巢狀在
with
語句中的塊。在塊退出後,生成器將恢復。如果在塊中發生未處理的異常,則會在生成器中發生 yield 的位置重新引發該異常。因此,可以使用try
...except
...finally
語句來捕獲錯誤(如果有),或確保進行一些清理。如果捕獲異常只是為了記錄它或執行一些操作(而不是完全抑制它),則生成器必須重新引發該異常。否則,生成器上下文管理器將向with
語句指示已處理該異常,並且執行將從緊跟with
語句的語句繼續。contextmanager()
使用ContextDecorator
,因此它建立的上下文管理器可以像裝飾器一樣使用,也可以在with
語句中使用。當用作裝飾器時,會在每次函式呼叫時隱式建立一個新的生成器例項(這允許contextmanager()
建立的原本 “一次性” 的上下文管理器滿足上下文管理器支援多次呼叫的要求,以便用作裝飾器)。在 3.2 版本中更改: 使用
ContextDecorator
。
- @contextlib.asynccontextmanager¶
類似於
contextmanager()
,但建立了一個非同步上下文管理器。此函式是一個 裝飾器,可用於定義
async with
語句非同步上下文管理器的工廠函式,而無需建立類或單獨的__aenter__()
和__aexit__()
方法。它必須應用於一個非同步生成器函式。一個簡單的例子
from contextlib import asynccontextmanager @asynccontextmanager async def get_connection(): conn = await acquire_db_connection() try: yield conn finally: await release_db_connection(conn) async def get_all_users(): async with get_connection() as conn: return conn.query('SELECT ...')
3.7 版本新增。
使用
asynccontextmanager()
定義的上下文管理器可以用作裝飾器或與async with
語句一起使用import time from contextlib import asynccontextmanager @asynccontextmanager async def timeit(): now = time.monotonic() try: yield finally: print(f'it took {time.monotonic() - now}s to run') @timeit() async def main(): # ... async code ...
當用作裝飾器時,會在每次函式呼叫時隱式建立一個新的生成器例項。這允許
asynccontextmanager()
建立的原本 “一次性” 的上下文管理器滿足上下文管理器支援多次呼叫的要求,以便用作裝飾器。在 3.10 版本中更改: 使用
asynccontextmanager()
建立的非同步上下文管理器可以用作裝飾器。
- contextlib.closing(thing)¶
返回一個上下文管理器,在程式碼塊執行完畢後關閉 thing。這基本上等同於:
from contextlib import contextmanager @contextmanager def closing(thing): try: yield thing finally: thing.close()
並允許你像這樣編寫程式碼:
from contextlib import closing from urllib.request import urlopen with closing(urlopen('https://python.club.tw')) as page: for line in page: print(line)
而無需顯式關閉
page
。即使發生錯誤,當with
程式碼塊退出時,也會呼叫page.close()
。
- contextlib.aclosing(thing)¶
返回一個非同步上下文管理器,該管理器在程式碼塊完成時呼叫 thing 的
aclose()
方法。這基本上等同於:from contextlib import asynccontextmanager @asynccontextmanager async def aclosing(thing): try: yield thing finally: await thing.aclose()
重要的是,當非同步生成器由於
break
或異常而提前退出時,aclosing()
支援對它們進行確定性的清理。例如:from contextlib import aclosing async with aclosing(my_generator()) as values: async for value in values: if value == 42: break
此模式確保生成器的非同步退出程式碼在其迭代的同一上下文中執行(以便異常和上下文變數按預期工作,並且退出程式碼不會在它所依賴的某些任務的生命週期之後執行)。
3.10 版本新增。
- contextlib.nullcontext(enter_result=None)¶
返回一個上下文管理器,它從
__enter__
返回 enter_result,但其他情況下什麼也不做。它旨在用作可選上下文管理器的替代,例如:def myfunction(arg, ignore_exceptions=False): if ignore_exceptions: # Use suppress to ignore all exceptions. cm = contextlib.suppress(Exception) else: # Do not ignore any exceptions, cm has no effect. cm = contextlib.nullcontext() with cm: # Do something
一個使用 enter_result 的例子:
def process_file(file_or_path): if isinstance(file_or_path, str): # If string, open file cm = open(file_or_path) else: # Caller is responsible for closing file cm = nullcontext(file_or_path) with cm as file: # Perform processing on the file
它也可以用作 非同步上下文管理器 的替代:
async def send_http(session=None): if not session: # If no http session, create it with aiohttp cm = aiohttp.ClientSession() else: # Caller is responsible for closing the session cm = nullcontext(session) async with cm as session: # Send http requests with session
3.7 版本新增。
在 3.10 版本中更改: 添加了對 非同步上下文管理器 的支援。
- contextlib.suppress(*exceptions)¶
返回一個上下文管理器,如果指定的異常發生在
with
語句的主體中,則會抑制這些異常,然後在with
語句結束後的第一條語句處恢復執行。與任何其他完全抑制異常的機制一樣,此上下文管理器應僅用於覆蓋非常特定的錯誤,在這種情況下,已知以靜默方式繼續程式執行是正確的做法。
例如:
from contextlib import suppress with suppress(FileNotFoundError): os.remove('somefile.tmp') with suppress(FileNotFoundError): os.remove('someotherfile.tmp')
這段程式碼等效於:
try: os.remove('somefile.tmp') except FileNotFoundError: pass try: os.remove('someotherfile.tmp') except FileNotFoundError: pass
此上下文管理器是可重入的。
如果
with
程式碼塊中的程式碼引發BaseExceptionGroup
,則將從組中刪除被抑制的異常。組中未被抑制的任何異常將使用原始組的derive()
方法建立一個新組,並在新組中重新引發。3.4 版本新增。
在 3.12 版本中更改:
suppress
現在支援抑制作為BaseExceptionGroup
的一部分引發的異常。
- contextlib.redirect_stdout(new_target)¶
用於將
sys.stdout
臨時重定向到另一個檔案或類檔案物件的上下文管理器。此工具為輸出硬連線到 stdout 的現有函式或類增加了靈活性。
例如,
help()
的輸出通常會發送到 sys.stdout。您可以透過將輸出重定向到io.StringIO
物件來捕獲字串中的輸出。替換流從__enter__
方法返回,因此可用作with
語句的目標。with redirect_stdout(io.StringIO()) as f: help(pow) s = f.getvalue()
要將
help()
的輸出傳送到磁碟上的檔案,請將輸出重定向到常規檔案:with open('help.txt', 'w') as f: with redirect_stdout(f): help(pow)
要將
help()
的輸出傳送到 sys.stderr:with redirect_stdout(sys.stderr): help(pow)
請注意,對
sys.stdout
的全域性副作用意味著此上下文管理器不適合在庫程式碼和大多數執行緒應用程式中使用。它也不會影響子程序的輸出。但是,對於許多實用程式指令碼來說,它仍然是一種有用的方法。此上下文管理器是可重入的。
3.4 版本新增。
- contextlib.redirect_stderr(new_target)¶
類似於
redirect_stdout()
,但將sys.stderr
重定向到另一個檔案或類檔案物件。此上下文管理器是可重入的。
3.5 版本新增。
- contextlib.chdir(path)¶
用於更改當前工作目錄的非並行安全上下文管理器。由於這會更改全域性狀態(即工作目錄),因此不適合在大多數執行緒或非同步上下文中使用。它也不適合大多數非線性程式碼執行,例如生成器(其中程式執行被暫時放棄)——除非明確需要,否則當此上下文管理器處於活動狀態時,你不應該 yield。
這是對
chdir()
的一個簡單包裝,它會在進入時更改當前工作目錄,並在退出時恢復舊的工作目錄。此上下文管理器是可重入的。
3.11 版本新增。
- class contextlib.ContextDecorator¶
一個基類,使上下文管理器也可以用作裝飾器。
從
ContextDecorator
繼承的上下文管理器必須像往常一樣實現__enter__
和__exit__
。__exit__
即使在用作裝飾器時,也保留其可選的異常處理功能。ContextDecorator
由contextmanager()
使用,因此您可以自動獲得此功能。ContextDecorator
的示例:from contextlib import ContextDecorator class mycontext(ContextDecorator): def __enter__(self): print('Starting') return self def __exit__(self, *exc): print('Finishing') return False
然後,該類可以像這樣使用:
>>> @mycontext() ... def function(): ... print('The bit in the middle') ... >>> function() Starting The bit in the middle Finishing >>> with mycontext(): ... print('The bit in the middle') ... Starting The bit in the middle Finishing
此更改只是以下形式的任何構造的語法糖:
def f(): with cm(): # Do stuff
ContextDecorator
允許您改為編寫:@cm() def f(): # Do stuff
它清楚地表明
cm
適用於整個函式,而不僅僅是它的一部分(並且節省一個縮排級別也很好)。可以使用
ContextDecorator
作為 mixin 類來擴充套件已經有基類的現有上下文管理器:from contextlib import ContextDecorator class mycontext(ContextBaseClass, ContextDecorator): def __enter__(self): return self def __exit__(self, *exc): return False
注意
由於被裝飾的函式必須能夠被多次呼叫,所以底層的上下文管理器必須支援在多個
with
語句中使用。如果不是這種情況,則應使用帶有函式內部顯式with
語句的原始構造。3.2 版本新增。
- class contextlib.AsyncContextDecorator¶
類似於
ContextDecorator
,但僅適用於非同步函式。AsyncContextDecorator
的示例from asyncio import run from contextlib import AsyncContextDecorator class mycontext(AsyncContextDecorator): async def __aenter__(self): print('Starting') return self async def __aexit__(self, *exc): print('Finishing') return False
然後,該類可以像這樣使用:
>>> @mycontext() ... async def function(): ... print('The bit in the middle') ... >>> run(function()) Starting The bit in the middle Finishing >>> async def function(): ... async with mycontext(): ... print('The bit in the middle') ... >>> run(function()) Starting The bit in the middle Finishing
3.10 版本新增。
- class contextlib.ExitStack¶
一個上下文管理器,旨在輕鬆地以程式設計方式組合其他上下文管理器和清理函式,特別是那些可選的或由輸入資料驅動的上下文管理器和清理函式。
例如,可以按照以下方式在一個 with 語句中輕鬆處理一組檔案
with ExitStack() as stack: files = [stack.enter_context(open(fname)) for fname in filenames] # All opened files will automatically be closed at the end of # the with statement, even if attempts to open files later # in the list raise an exception
__enter__()
方法返回ExitStack
例項,並且不執行其他操作。每個例項都維護一個已註冊的回撥堆疊,當例項關閉時(顯式關閉或在
with
語句末尾隱式關閉)會以相反的順序呼叫這些回撥。請注意,當上下文堆疊例項被垃圾回收時,不會隱式呼叫回撥。使用此堆疊模型是為了能夠正確處理在其
__init__
方法中獲取其資源的上下文管理器(例如檔案物件)。由於註冊的回撥以註冊的相反順序呼叫,因此最終的行為就像使用了多個巢狀的
with
語句,其中包含已註冊的回撥集。這甚至擴充套件到異常處理 - 如果內部回撥抑制或替換了異常,則外部回撥將根據更新後的狀態傳遞引數。這是一個相對底層的 API,負責正確展開退出回撥堆疊的細節。它為以特定於應用程式的方式操作退出堆疊的更高級別的上下文管理器提供了合適的基礎。
3.3 版本新增。
- enter_context(cm)¶
進入新的上下文管理器,並將其
__exit__()
方法新增到回撥堆疊。返回值是上下文管理器自己的__enter__()
方法的結果。這些上下文管理器可以像直接作為
with
語句的一部分使用時一樣抑制異常。在 3.11 版本中更改: 如果 cm 不是上下文管理器,則引發
TypeError
而不是AttributeError
。
- push(exit)¶
將上下文管理器的
__exit__()
方法新增到回撥堆疊。由於 不 呼叫
__enter__
,因此可以使用此方法用上下文管理器自己的__exit__()
方法來覆蓋__enter__()
實現的一部分。如果傳遞的物件不是上下文管理器,則此方法假定它是具有與上下文管理器的
__exit__()
方法相同簽名的回撥,並將其直接新增到回撥堆疊。透過返回 true 值,這些回撥可以像上下文管理器
__exit__()
方法一樣抑制異常。傳遞的物件從函式返回,允許將此方法用作函式裝飾器。
- callback(callback, /, *args, **kwds)¶
接受任意回撥函式和引數,並將其新增到回撥堆疊。
與其他方法不同,以這種方式新增的回撥無法抑制異常(因為它們永遠不會傳遞異常詳細資訊)。
傳遞的回撥從函式返回,允許將此方法用作函式裝飾器。
- pop_all()¶
將回調堆疊傳輸到新的
ExitStack
例項並返回它。此操作不會呼叫任何回撥 - 相反,它們現在將在新的堆疊關閉時呼叫(顯式關閉或在with
語句末尾隱式關閉)。例如,可以按照以下方式以“全有或全無”的操作開啟一組檔案
with ExitStack() as stack: files = [stack.enter_context(open(fname)) for fname in filenames] # Hold onto the close method, but don't call it yet. close_files = stack.pop_all().close # If opening any file fails, all previously opened files will be # closed automatically. If all files are opened successfully, # they will remain open even after the with statement ends. # close_files() can then be invoked explicitly to close them all.
- close()¶
立即展開回調堆疊,以註冊的相反順序呼叫回撥。對於註冊的任何上下文管理器和退出回撥,傳遞的引數將指示沒有發生異常。
- class contextlib.AsyncExitStack¶
一個 非同步上下文管理器,類似於
ExitStack
,支援組合同步和非同步上下文管理器,以及具有用於清理邏輯的協程。- coroutine enter_async_context(cm)¶
類似於
ExitStack.enter_context()
,但期望一個非同步上下文管理器。在 3.11 版本中更改: 如果 cm 不是非同步上下文管理器,則引發
TypeError
而不是AttributeError
。
- push_async_exit(exit)¶
類似於
ExitStack.push()
,但期望一個非同步上下文管理器或一個協程函式。
- push_async_callback(callback, /, *args, **kwds)¶
類似於
ExitStack.callback()
,但期望一個協程函式。
- coroutine aclose()¶
類似於
ExitStack.close()
,但能正確處理 awaitables。
繼續
asynccontextmanager()
的示例async with AsyncExitStack() as stack: connections = [await stack.enter_async_context(get_connection()) for i in range(5)] # All opened connections will automatically be released at the end of # the async with statement, even if attempts to open a connection # later in the list raise an exception.
3.7 版本新增。
示例和方法¶
本節介紹一些示例和方法,以便有效地利用 contextlib
提供的工具。
支援可變數量的上下文管理器¶
ExitStack
的主要用例是在類文件中給出的:在單個 with
語句中支援可變數量的上下文管理器和其他清理操作。這種可變性可能來自於所需上下文管理器的數量由使用者輸入驅動(例如開啟使用者指定的檔案集合),或者來自於某些上下文管理器是可選的。
with ExitStack() as stack:
for resource in resources:
stack.enter_context(resource)
if need_special_resource():
special = acquire_special_resource()
stack.callback(release_special_resource, special)
# Perform operations that use the acquired resources
捕獲 __enter__
方法中的異常¶
有時,需要捕獲 __enter__
方法實現中的異常,而不會 無意中捕獲來自 with
語句主體或上下文管理器的 __exit__
方法的異常。透過使用 ExitStack
,可以稍微分離上下文管理協議中的步驟,以便允許這樣做
stack = ExitStack()
try:
x = stack.enter_context(cm)
except Exception:
# handle __enter__ exception
else:
with stack:
# Handle normal case
實際上需要這樣做可能表明底層 API 應該提供一個直接的資源管理介面,以便與 try
/except
/finally
語句一起使用,但並非所有的 API 在這方面都設計良好。當上下文管理器是唯一提供的資源管理 API 時,ExitStack
可以更輕鬆地處理無法在 with
語句中直接處理的各種情況。
在 __enter__
實現中進行清理¶
正如 ExitStack.push()
的文件中所述,如果 __enter__()
實現中的後續步驟失敗,則此方法可用於清理已分配的資源。
這是一個示例,說明如何為接受資源獲取和釋放函式以及可選的驗證函式的上下文管理器執行此操作,並將其對映到上下文管理協議
from contextlib import contextmanager, AbstractContextManager, ExitStack
class ResourceManager(AbstractContextManager):
def __init__(self, acquire_resource, release_resource, check_resource_ok=None):
self.acquire_resource = acquire_resource
self.release_resource = release_resource
if check_resource_ok is None:
def check_resource_ok(resource):
return True
self.check_resource_ok = check_resource_ok
@contextmanager
def _cleanup_on_error(self):
with ExitStack() as stack:
stack.push(self)
yield
# The validation check passed and didn't raise an exception
# Accordingly, we want to keep the resource, and pass it
# back to our caller
stack.pop_all()
def __enter__(self):
resource = self.acquire_resource()
with self._cleanup_on_error():
if not self.check_resource_ok(resource):
msg = "Failed validation for {!r}"
raise RuntimeError(msg.format(resource))
return resource
def __exit__(self, *exc_details):
# We don't need to duplicate any of our resource release logic
self.release_resource()
替換任何使用 try-finally
和標誌變數的情況¶
您有時會看到的一種模式是帶有標誌變數的 try-finally
語句,用於指示是否應執行 finally
子句的主體。在其最簡單的形式(不能僅僅透過使用 except
子句來處理)中,它看起來像這樣
cleanup_needed = True
try:
result = perform_operation()
if result:
cleanup_needed = False
finally:
if cleanup_needed:
cleanup_resources()
與任何基於 try
語句的程式碼一樣,這可能會給開發和審查帶來問題,因為設定程式碼和清理程式碼最終可能會被任意長的程式碼段分隔開。
ExitStack
可以註冊一個回撥函式,以便在 with
語句結束時執行,然後稍後決定是否跳過執行該回調函式
from contextlib import ExitStack
with ExitStack() as stack:
stack.callback(cleanup_resources)
result = perform_operation()
if result:
stack.pop_all()
這允許預先明確預期的清理行為,而不是需要單獨的標誌變數。
如果特定應用程式大量使用這種模式,則可以透過一個小的輔助類進一步簡化它
from contextlib import ExitStack
class Callback(ExitStack):
def __init__(self, callback, /, *args, **kwds):
super().__init__()
self.callback(callback, *args, **kwds)
def cancel(self):
self.pop_all()
with Callback(cleanup_resources) as cb:
result = perform_operation()
if result:
cb.cancel()
如果資源清理尚未整齊地捆綁到一個獨立的函式中,則仍然可以使用 ExitStack.callback()
的裝飾器形式來預先宣告資源清理
from contextlib import ExitStack
with ExitStack() as stack:
@stack.callback
def cleanup_resources():
...
result = perform_operation()
if result:
stack.pop_all()
由於裝飾器協議的工作方式,以這種方式宣告的回撥函式不能接受任何引數。相反,任何要釋放的資源都必須作為閉包變數訪問。
使用上下文管理器作為函式裝飾器¶
ContextDecorator
使得既可以在普通的 with
語句中使用上下文管理器,也可以作為函式裝飾器使用。
例如,有時用一個記錄器來包裝函式或語句組很有用,該記錄器可以跟蹤進入時間和退出時間。與其為該任務編寫函式裝飾器和上下文管理器,不如從 ContextDecorator
繼承,在一個定義中提供兩種功能
from contextlib import ContextDecorator
import logging
logging.basicConfig(level=logging.INFO)
class track_entry_and_exit(ContextDecorator):
def __init__(self, name):
self.name = name
def __enter__(self):
logging.info('Entering: %s', self.name)
def __exit__(self, exc_type, exc, exc_tb):
logging.info('Exiting: %s', self.name)
此類的例項可以用作上下文管理器
with track_entry_and_exit('widget loader'):
print('Some time consuming activity goes here')
load_widget()
也可以作為函式裝飾器
@track_entry_and_exit('widget loader')
def activity():
print('Some time consuming activity goes here')
load_widget()
請注意,當將上下文管理器用作函式裝飾器時,還有一個額外的限制:無法訪問 __enter__()
的返回值。如果需要該值,則仍然必須使用顯式的 with
語句。
單次使用、可重用和可重入的上下文管理器¶
大多數上下文管理器的編寫方式意味著它們只能在 with
語句中使用一次。這些單次使用的上下文管理器每次使用時都必須重新建立 - 嘗試第二次使用它們會觸發異常或以其他方式無法正常工作。
這個常見的限制意味著通常建議在 with
語句的頭部直接建立上下文管理器,在這些語句中使用它們(如上面的所有用法示例所示)。
檔案是有效的單次使用上下文管理器的示例,因為第一個 with
語句將關閉該檔案,阻止使用該檔案物件進行任何進一步的 IO 操作。
使用 contextmanager()
建立的上下文管理器也是單次使用的上下文管理器,如果嘗試第二次使用它們,將會報錯,因為底層的生成器未能 yield。
>>> from contextlib import contextmanager
>>> @contextmanager
... def singleuse():
... print("Before")
... yield
... print("After")
...
>>> cm = singleuse()
>>> with cm:
... pass
...
Before
After
>>> with cm:
... pass
...
Traceback (most recent call last):
...
RuntimeError: generator didn't yield
可重入上下文管理器¶
更復雜的上下文管理器可能是“可重入”的。 這些上下文管理器不僅可以在多個 with
語句中使用,還可以在已經使用相同上下文管理器的 with
語句內部使用。
threading.RLock
是一個可重入上下文管理器的例子,suppress()
, redirect_stdout()
和 chdir()
也是如此。 以下是一個非常簡單的可重入使用示例
>>> from contextlib import redirect_stdout
>>> from io import StringIO
>>> stream = StringIO()
>>> write_to_stream = redirect_stdout(stream)
>>> with write_to_stream:
... print("This is written to the stream rather than stdout")
... with write_to_stream:
... print("This is also written to the stream")
...
>>> print("This is written directly to stdout")
This is written directly to stdout
>>> print(stream.getvalue())
This is written to the stream rather than stdout
This is also written to the stream
現實世界中可重入的例子更可能涉及到多個函式相互呼叫,因此會比這個例子複雜得多。
還要注意,可重入 *不是* 與執行緒安全相同的概念。例如,redirect_stdout()
絕對不是執行緒安全的,因為它透過將 sys.stdout
繫結到不同的流來對系統狀態進行全域性修改。
可複用上下文管理器¶
與單次使用和可重入上下文管理器不同的是“可複用”上下文管理器(或者,更明確地說,“可複用,但不可重入”的上下文管理器,因為可重入的上下文管理器也是可複用的)。這些上下文管理器支援多次使用,但是如果特定的上下文管理器例項已經在包含的 with 語句中使用過,則會失敗(或以其他方式無法正常工作)。
threading.Lock
是一個可複用但不可重入的上下文管理器的例子(對於可重入鎖,必須使用 threading.RLock
代替)。
另一個可複用但不可重入的上下文管理器的例子是 ExitStack
,因為它在離開任何 with 語句時都會呼叫 *所有* 當前註冊的回撥,無論這些回撥是在哪裡新增的。
>>> from contextlib import ExitStack
>>> stack = ExitStack()
>>> with stack:
... stack.callback(print, "Callback: from first context")
... print("Leaving first context")
...
Leaving first context
Callback: from first context
>>> with stack:
... stack.callback(print, "Callback: from second context")
... print("Leaving second context")
...
Leaving second context
Callback: from second context
>>> with stack:
... stack.callback(print, "Callback: from outer context")
... with stack:
... stack.callback(print, "Callback: from inner context")
... print("Leaving inner context")
... print("Leaving outer context")
...
Leaving inner context
Callback: from inner context
Callback: from outer context
Leaving outer context
正如示例的輸出所示,在多個 with 語句中重用單個堆疊物件可以正常工作,但是嘗試巢狀它們會導致堆疊在最內層的 with 語句結束時被清除,這不太可能是期望的行為。
使用單獨的 ExitStack
例項而不是重用單個例項可以避免該問題
>>> from contextlib import ExitStack
>>> with ExitStack() as outer_stack:
... outer_stack.callback(print, "Callback: from outer context")
... with ExitStack() as inner_stack:
... inner_stack.callback(print, "Callback: from inner context")
... print("Leaving inner context")
... print("Leaving outer context")
...
Leaving inner context
Callback: from inner context
Leaving outer context
Callback: from outer context