contextlibUtilities for with-statement contextswith语句上下文的实用程序

Source code: Lib/contextlib.py


This module provides utilities for common tasks involving the with statement. 此模块为涉及with语句的常见任务提供实用程序。For more information see also Context Manager Types and With Statement Context Managers.有关更多信息,请参阅上下文管理器类型With语句上下文管理器

Utilities公用事业

Functions and classes provided:提供的功能和类:

classcontextlib.AbstractContextManager

An abstract base class for classes that implement object.__enter__() and object.__exit__(). 实现object.__enter__()object.__exit__()的类的抽象基类__输入__()A default implementation for object.__enter__() is provided which returns self while object.__exit__() is an abstract method which by default returns None. See also the definition of Context Manager Types.提供了object.__enter__()的默认实现enter__(),它返回self同时对象object.__exit__()是一个抽象方法,默认情况下返回None。另请参见上下文管理器类型的定义

New in version 3.6.版本3.6中新增。

classcontextlib.AbstractAsyncContextManager

An abstract base class for classes that implement object.__aenter__() and object.__aexit__(). 实现object.__aenter__()object.__aexit__()的类的抽象基类A default implementation for object.__aenter__() is provided which returns self while object.__aexit__() is an abstract method which by default returns None. 提供了object.__aenter__()的默认实现提供了aenter__(),它返回self,同时object.__aexit__()是一个抽象方法,默认情况下返回NoneSee also the definition of Asynchronous Context Managers.另请参见异步上下文管理器的定义。

New in version 3.7.版本3.7中新增。

@contextlib.contextmanager

This function is a decorator that can be used to define a factory function for with statement context managers, without needing to create a class or separate __enter__() and __exit__() methods.此函数是一个装饰器,可用于定义with语句上下文管理器的工厂函数,而无需创建类或单独的__enter__()__exit__()方法。

While many objects natively support use in with statements, sometimes a resource needs to be managed that isn’t a context manager in its own right, and doesn’t implement a close() method for use with contextlib.closing虽然许多对象本机支持在语句中使用,但有时需要管理的资源本身不是上下文管理器,并且没有实现用于contextlib.closingclose()方法

An abstract example would be the following to ensure correct resource management:以下是一个抽象的例子,以确保正确的资源管理:

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

The function being decorated must return a generator-iterator when called. 被修饰的函数在被调用时必须返回生成器迭代器。This iterator must yield exactly one value, which will be bound to the targets in the with statement’s as clause, if any.此迭代器必须只产生一个值,该值将绑定到with语句的as子句中的目标(如果有的话)。

At the point where the generator yields, the block nested in the with statement is executed. 在生成器生成时,执行嵌套在with语句中的块。The generator is then resumed after the block is exited. 然后,在块退出后恢复生成器。If an unhandled exception occurs in the block, it is reraised inside the generator at the point where the yield occurred. 如果块中发生未经处理的异常,则会在生成程序内部发生屈服点时重新引发该异常。Thus, you can use a tryexceptfinally statement to trap the error (if any), or ensure that some cleanup takes place. 因此,您可以使用tryexceptfinally语句来捕获错误(如果有的话),或者确保进行一些清理。If an exception is trapped merely in order to log it or to perform some action (rather than to suppress it entirely), the generator must reraise that exception. 如果捕获异常只是为了记录它或执行某些操作(而不是完全抑制它),则生成器必须重新评估该异常。Otherwise the generator context manager will indicate to the with statement that the exception has been handled, and execution will resume with the statement immediately following the with statement.否则,生成器上下文管理器将向with语句指示异常已被处理,并在with语句之后立即使用该语句恢复执行。

contextmanager() uses ContextDecorator so the context managers it creates can be used as decorators as well as in with statements. contextmanager()使用ContextDecorator,因此它创建的上下文管理器既可以用作装饰器,也可以用于with语句。When used as a decorator, a new generator instance is implicitly created on each function call (this allows the otherwise “one-shot” context managers created by contextmanager() to meet the requirement that context managers support multiple invocations in order to be used as decorators).当用作装饰器时,会在每个函数调用上隐式创建一个新的生成器实例(这允许contextmanager()创建的其他“一次性”上下文管理器满足上下文管理器支持多个调用才能用作装饰器的要求)。

Changed in version 3.2:版本3.2中更改: Use of ContextDecorator.ContextDecorator的使用。

@contextlib.asynccontextmanager

Similar to contextmanager(), but creates an asynchronous context manager.类似于contextmanager(),但创建了一个异步上下文管理器

This function is a decorator that can be used to define a factory function for async with statement asynchronous context managers, without needing to create a class or separate __aenter__() and __aexit__() methods. 此函数是一个装饰器,可用于定义async with语句异步上下文管理器的工厂函数,而无需创建类或单独的__aenter__()__aexit_()方法。It must be applied to an asynchronous generator function.它必须应用于异步生成器函数。

A simple example:一个简单的例子:

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 ...')

New in version 3.7.版本3.7中新增。

Context managers defined with asynccontextmanager() can be used either as decorators or with async with statements: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 ...

When used as a decorator, a new generator instance is implicitly created on each function call. 当用作装饰器时,会在每个函数调用上隐式创建一个新的生成器实例。This allows the otherwise “one-shot” context managers created by asynccontextmanager() to meet the requirement that context managers support multiple invocations in order to be used as decorators.这允许asynccontextmanager()创建的其他“一次性”上下文管理器满足上下文管理器支持多个调用以便用作装饰器的要求。

Changed in version 3.10:版本3.10中更改: Async context managers created with asynccontextmanager() can be used as decorators.使用asynccontextmanager()创建的异步上下文管理器可以用作装饰器。

contextlib.closing(thing)

Return a context manager that closes thing upon completion of the block. This is basically equivalent to:返回一个上下文管理器,该管理器在块完成时关闭对象。这基本上相当于:

from contextlib import contextmanager
@contextmanager
def closing(thing):
try:
yield thing
finally:
thing.close()

And lets you write code like this:让您编写这样的代码:

from contextlib import closing
from urllib.request import urlopen
with closing(urlopen('https://www.python.org')) as page:
for line in page:
print(line)

without needing to explicitly close page. 而无需显式关闭pageEven if an error occurs, page.close() will be called when the with block is exited.即使出现错误,在退出with块时也会调用page.close()

classcontextlib.aclosing(thing)

Return an async context manager that calls the aclose() method of thing upon completion of the block. 返回一个异步上下文管理器,该管理器在块完成时调用事物aclose()方法。This is basically equivalent to:这基本上相当于:

from contextlib import asynccontextmanager
@asynccontextmanager
async def aclosing(thing):
try:
yield thing
finally:
await thing.aclose()

Significantly, aclosing() supports deterministic cleanup of async generators when they happen to exit early by break or an exception. For example:值得注意的是,当异步生成器由于break或异常而提前退出时,aclosing()支持异步生成器的确定性清理。例如:

from contextlib import aclosing
async with aclosing(my_generator()) as values:
async for value in values:
if value == 42:
break

This pattern ensures that the generator’s async exit code is executed in the same context as its iterations (so that exceptions and context variables work as expected, and the exit code isn’t run after the lifetime of some task it depends on).此模式确保生成器的异步退出代码在与其迭代相同的上下文中执行(以便异常和上下文变量按预期工作,并且退出代码不会在它所依赖的某个任务的生存期之后运行)。

New in version 3.10.版本3.10中新增。

contextlib.nullcontext(enter_result=None)

Return a context manager that returns enter_result from __enter__, but otherwise does nothing. 返回一个上下文管理器,该管理器从__enter__返回enter_result,但在其他情况下不执行任何操作。It is intended to be used as a stand-in for an optional context manager, for example:它旨在用作可选上下文管理器的替身,例如:

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

An example using enter_result:使用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

It can also be used as a stand-in for asynchronous context managers:它还可以用作异步上下文管理器的替身:

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

New in version 3.7.版本3.7中新增。

Changed in version 3.10:版本3.10中更改: asynchronous context manager support was added.添加了异步上下文管理器支持。

contextlib.suppress(*exceptions)

Return a context manager that suppresses any of the specified exceptions if they occur in the body of a with statement and then resumes execution with the first statement following the end of the with statement.返回一个上下文管理器,如果指定的异常出现在with语句的正文中,则该上下文管理器将抑制这些异常,然后使用with语句结束后的第一个语句继续执行。

As with any other mechanism that completely suppresses exceptions, this context manager should be used only to cover very specific errors where silently continuing with program execution is known to be the right thing to do.与任何其他完全抑制异常的机制一样,此上下文管理器应仅用于覆盖非常特定的错误,在这些错误中,静默地继续执行程序是正确的做法。

For example:

from contextlib import suppress
with suppress(FileNotFoundError):
os.remove('somefile.tmp')

with suppress(FileNotFoundError):
os.remove('someotherfile.tmp')

This code is equivalent to:此代码等效于:

try:
os.remove('somefile.tmp')
except FileNotFoundError:
pass
try:
os.remove('someotherfile.tmp')
except FileNotFoundError:
pass

This context manager is reentrant.此上下文管理器是可重入的

New in version 3.4.版本3.4中新增。

contextlib.redirect_stdout(new_target)

Context manager for temporarily redirecting sys.stdout to another file or file-like object.上下文管理器,用于将sys.stdout临时重定向到另一个文件或类似文件的对象。

This tool adds flexibility to existing functions or classes whose output is hardwired to stdout.该工具为其输出硬连接到stdout的现有函数或类增加了灵活性。

For example, the output of help() normally is sent to sys.stdout. 例如,help()的输出通常发送到sys.stdoutYou can capture that output in a string by redirecting the output to an io.StringIO object. 通过将输出重定向到io.StringIO对象,可以在字符串中捕获该输出。The replacement stream is returned from the __enter__ method and so is available as the target of the with statement:替换流是从__enter__方法返回的,因此可用作with语句的目标:

with redirect_stdout(io.StringIO()) as f:
help(pow)
s = f.getvalue()

To send the output of help() to a file on disk, redirect the output to a regular file:要将help()的输出发送到磁盘上的文件,请将输出重定向到常规文件:

with open('help.txt', 'w') as f:
with redirect_stdout(f):
help(pow)

To send the output of help() to sys.stderr:要将help()的输出发送到sys.stderr,请执行以下操作:

with redirect_stdout(sys.stderr):
help(pow)

Note that the global side effect on sys.stdout means that this context manager is not suitable for use in library code and most threaded applications. 请注意,sys.stdout的全局副作用意味着此上下文管理器不适合在库代码和大多数线程应用程序中使用。It also has no effect on the output of subprocesses. 它也对子流程的输出没有影响。However, it is still a useful approach for many utility scripts.然而,对于许多实用程序脚本来说,它仍然是一种有用的方法。

This context manager is reentrant.此上下文管理器是可重入的

New in version 3.4.版本3.4中新增。

contextlib.redirect_stderr(new_target)

Similar to redirect_stdout() but redirecting sys.stderr to another file or file-like object.类似于redirect_stdout(),但将sys.stderr重定向到另一个文件或类似文件的对象。

This context manager is reentrant.此上下文管理器是可重入的

New in version 3.5.版本3.5中新增。

classcontextlib.ContextDecorator

A base class that enables a context manager to also be used as a decorator.一个基类,它使上下文管理器也可以用作装饰器。

Context managers inheriting from ContextDecorator have to implement __enter__ and __exit__ as normal. ContextDecorator继承的上下文管理器必须像往常一样实现__enter____exit____exit__ retains its optional exception handling even when used as a decorator.即使用作装饰器,也保留其可选的异常处理。

ContextDecorator is used by contextmanager(), so you get this functionality automatically.ContextDecoratorcontextmanager()使用,因此您可以自动获得此功能。

Example of ContextDecorator: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

This change is just syntactic sugar for any construct of the following form:这种变化只是以下形式的任何结构的句法糖:

def f():
with cm():
# Do stuff

ContextDecorator lets you instead write:让您改为编写:

@cm()
def f():
# Do stuff

It makes it clear that the cm applies to the whole function, rather than just a piece of it (and saving an indentation level is nice, too).它清楚地表明,cm适用于整个函数,而不仅仅是其中的一部分(保存缩进级别也很好)。

Existing context managers that already have a base class can be extended by using ContextDecorator as a mixin class:已经有基类的现有上下文管理器可以通过使用ContextDecorator作为mixin类进行扩展:

from contextlib import ContextDecorator
class mycontext(ContextBaseClass, ContextDecorator):
def __enter__(self):
return self

def __exit__(self, *exc):
return False

Note

As the decorated function must be able to be called multiple times, the underlying context manager must support use in multiple with statements. 由于修饰函数必须能够被多次调用,因此底层上下文管理器必须支持在多个with语句中使用。If this is not the case, then the original construct with the explicit with statement inside the function should be used.如果不是这种情况,那么应该使用函数内部带有显式with语句的原始构造。

New in version 3.2.版本3.2中新增。

classcontextlib.AsyncContextDecorator

Similar to ContextDecorator but only for asynchronous functions.类似于ContextDecorator,但仅适用于异步函数。

Example of AsyncContextDecorator: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

New in version 3.10.版本3.10中新增。

classcontextlib.ExitStack

A context manager that is designed to make it easy to programmatically combine other context managers and cleanup functions, especially those that are optional or otherwise driven by input data.一种上下文管理器,其设计目的是使其他上下文管理器和清理函数(尤其是那些可选的或由输入数据驱动的函数)能够以编程方式轻松组合。

For example, a set of files may easily be handled in a single with statement as follows:例如,一组文件可以很容易地在单个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

The __enter__() method returns the ExitStack instance, and performs no additional operations.__enter__()方法返回ExitStack实例,不执行其他操作。

Each instance maintains a stack of registered callbacks that are called in reverse order when the instance is closed (either explicitly or implicitly at the end of a with statement). 每个实例都维护一个已注册回调堆栈,当实例关闭时(在with语句末尾显式或隐式),这些回调将以相反的顺序调用。Note that callbacks are not invoked implicitly when the context stack instance is garbage collected.请注意,当上下文堆栈实例被垃圾回收时,回调不会被隐式调用。

This stack model is used so that context managers that acquire their resources in their __init__ method (such as file objects) can be handled correctly.使用此堆栈模型可以正确处理在__init__方法中获取资源的上下文管理器(如文件对象)。

Since registered callbacks are invoked in the reverse order of registration, this ends up behaving as if multiple nested with statements had been used with the registered set of callbacks. 由于已注册的回调是以与注册相反的顺序调用的,因此最终的行为就像已注册的一组回调使用了多个嵌套的with语句一样。This even extends to exception handling - if an inner callback suppresses or replaces an exception, then outer callbacks will be passed arguments based on that updated state.这甚至扩展到异常处理——如果内部回调抑制或替换异常,那么外部回调将根据更新后的状态传递参数。

This is a relatively low level API that takes care of the details of correctly unwinding the stack of exit callbacks. It provides a suitable foundation for higher level context managers that manipulate the exit stack in application specific ways.这是一个相对较低级别的API,负责正确展开退出回调堆栈的细节。它为以特定于应用程序的方式操作出口堆栈的更高级别上下文管理器提供了合适的基础。

New in version 3.3.版本3.3中新增。

enter_context(cm)

Enters a new context manager and adds its __exit__() method to the callback stack. The return value is the result of the context manager’s own __enter__() method.进入一个新的上下文管理器,并将其__exit_()方法添加到回调堆栈中。返回值是上下文管理器自己的__enter__()方法的结果。

These context managers may suppress exceptions just as they normally would if used directly as part of a with statement.如果直接作为with语句的一部分使用,这些上下文管理器可能会抑制异常,就像通常情况下一样。

push(exit)

Adds a context manager’s __exit__() method to the callback stack.将上下文管理器的__exit_()方法添加到回调堆栈中。

As __enter__ is not invoked, this method can be used to cover part of an __enter__() implementation with a context manager’s own __exit__() method.由于调用__enter__,此方法可用于用上下文管理器自己的__exit__()方法覆盖__enter__()实现的一部分。

If passed an object that is not a context manager, this method assumes it is a callback with the same signature as a context manager’s __exit__() method and adds it directly to the callback stack.如果传递的对象不是上下文管理器,则此方法假定它是一个回调,具有与上下文管理器的__exit_()方法相同的签名,并将其直接添加到回调堆栈中。

By returning true values, these callbacks can suppress exceptions the same way context manager __exit__() methods can.通过返回真值,这些回调可以像上下文管理器__exit_()方法一样抑制异常。

The passed in object is returned from the function, allowing this method to be used as a function decorator.传入的对象是从函数返回的,允许将此方法用作函数装饰器。

callback(callback, /, *args, **kwds)

Accepts an arbitrary callback function and arguments and adds it to the callback stack.接受任意回调函数和参数,并将其添加到回调堆栈中。

Unlike the other methods, callbacks added this way cannot suppress exceptions (as they are never passed the exception details).与其他方法不同,以这种方式添加的回调无法抑制异常(因为它们从未传递异常详细信息)。

The passed in callback is returned from the function, allowing this method to be used as a function decorator.传入的回调是从函数返回的,允许将此方法用作函数装饰器。

pop_all()

Transfers the callback stack to a fresh ExitStack instance and returns it. 将回调堆栈传输到新的ExitStack实例并返回它。No callbacks are invoked by this operation - instead, they will now be invoked when the new stack is closed (either explicitly or implicitly at the end of a with statement).此操作不会调用回调,而是在关闭新堆栈时(在with语句末尾显式或隐式)调用它们。

For example, a group of files can be opened as an “all or nothing” operation as follows:例如,一组文件可以作为“要么全部打开,要么什么都不打开”操作打开,如下所示:

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()

Immediately unwinds the callback stack, invoking callbacks in the reverse order of registration. 立即展开回调堆栈,以与注册相反的顺序调用回调。For any context managers and exit callbacks registered, the arguments passed in will indicate that no exception occurred.对于任何已注册的上下文管理器和退出回调,传入的参数将指示没有发生异常。

classcontextlib.AsyncExitStack

An asynchronous context manager, similar to ExitStack, that supports combining both synchronous and asynchronous context managers, as well as having coroutines for cleanup logic.一个异步上下文管理器,类似于ExitStack,支持组合同步和异步上下文管理程序,并具有用于清理逻辑的协同程序。

The close() method is not implemented, aclose() must be used instead.未实现close()方法,必须改用aclose()

coroutineenter_async_context(cm)

Similar to enter_context() but expects an asynchronous context manager.类似于enter_context(),但需要一个异步上下文管理器。

push_async_exit(exit)

Similar to push() but expects either an asynchronous context manager or a coroutine function.类似于push(),但需要异步上下文管理器或协程函数。

push_async_callback(callback, /, *args, **kwds)

Similar to callback() but expects a coroutine function.类似于callback(),但需要一个协程函数。

coroutineaclose()

Similar to close() but properly handles awaitables.类似于close(),但可以正确处理可用的内容。

Continuing the example for asynccontextmanager():继续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.

New in version 3.7.版本3.7中新增。

Examples and Recipes示例和食谱

This section describes some examples and recipes for making effective use of the tools provided by contextlib.本节介绍了一些有效使用contextlib提供的工具的示例和方法。

Supporting a variable number of context managers支持可变数量的上下文管理器

The primary use case for ExitStack is the one given in the class documentation: supporting a variable number of context managers and other cleanup operations in a single with statement. ExitStack的主要用例是类文档中给出的:在单个with语句中支持可变数量的上下文管理器和其他清理操作。The variability may come from the number of context managers needed being driven by user input (such as opening a user specified collection of files), or from some of the context managers being optional:可变性可能来自于由用户输入(例如打开用户指定的文件集合)驱动所需的上下文管理器的数量,或者来自于一些可选的上下文管理程序:

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

As shown, ExitStack also makes it quite easy to use with statements to manage arbitrary resources that don’t natively support the context management protocol.如图所示,ExitStack还可以很容易地与with语句一起使用,以管理本机不支持上下文管理协议的任意资源。

Catching exceptions from __enter__ methods__enter__方法捕获异常

It is occasionally desirable to catch exceptions from an __enter__ method implementation, without inadvertently catching exceptions from the with statement body or the context manager’s __exit__ method. 偶尔需要从__enter__方法实现捕获异常,而不会无意中从with语句体或上下文管理器的__exit__方法捕获异常。By using ExitStack the steps in the context management protocol can be separated slightly in order to allow this:通过使用ExitStack,上下文管理协议中的步骤可以稍微分开,以便实现以下目的:

stack = ExitStack()
try:
x = stack.enter_context(cm)
except Exception:
# handle __enter__ exception
else:
with stack:
# Handle normal case

Actually needing to do this is likely to indicate that the underlying API should be providing a direct resource management interface for use with try/except/finally statements, but not all APIs are well designed in that regard. 实际上需要这样做可能表明底层API应该提供一个直接的资源管理接口,用于try/except/finally语句,但并不是所有的API都在这方面设计得很好。When a context manager is the only resource management API provided, then ExitStack can make it easier to handle various situations that can’t be handled directly in a with statement.当上下文管理器是提供的唯一资源管理API时,ExitStack可以更容易地处理不能在with语句中直接处理的各种情况。

Cleaning up in an __enter__ implementation__enter__实现中进行清理

As noted in the documentation of ExitStack.push(), this method can be useful in cleaning up an already allocated resource if later steps in the __enter__() implementation fail.ExitStack.push()的文档中所述,如果__enter__()实现中的后续步骤失败,此方法在清理已分配的资源时非常有用。

Here’s an example of doing this for a context manager that accepts resource acquisition and release functions, along with an optional validation function, and maps them to the context management protocol:以下是一个为上下文管理器执行此操作的示例,该上下文管理器接受资源获取和释放功能以及可选的验证功能,并将它们映射到上下文管理协议:

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()

Replacing any use of try-finally and flag variables替换try-finally和标记变量的任何使用

A pattern you will sometimes see is a try-finally statement with a flag variable to indicate whether or not the body of the finally clause should be executed. 您有时会看到一种模式,即带有标志变量的try-finally语句,用于指示是否应执行finally子句的主体。In its simplest form (that can’t already be handled just by using an except clause instead), it looks something like this:在最简单的形式中(不能仅通过使用except子句来处理),它看起来是这样的:

cleanup_needed = True
try:
result = perform_operation()
if result:
cleanup_needed = False
finally:
if cleanup_needed:
cleanup_resources()

As with any try statement based code, this can cause problems for development and review, because the setup code and the cleanup code can end up being separated by arbitrarily long sections of code.与任何基于try语句的代码一样,这可能会给开发和审查带来问题,因为设置代码和清理代码最终可能会被任意长的代码段分隔开。

ExitStack makes it possible to instead register a callback for execution at the end of a with statement, and then later decide to skip executing that callback:使得可以在with语句结束时注册一个回调以供执行,然后决定跳过执行该回调:

from contextlib import ExitStack
with ExitStack() as stack:
stack.callback(cleanup_resources)
result = perform_operation()
if result:
stack.pop_all()

This allows the intended cleanup up behaviour to be made explicit up front, rather than requiring a separate flag variable.这允许预先明确预期的清理行为,而不需要单独的标志变量。

If a particular application uses this pattern a lot, it can be simplified even further by means of a small helper class:如果一个特定的应用程序经常使用这种模式,则可以通过一个小的辅助类来进一步简化它:

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()

If the resource cleanup isn’t already neatly bundled into a standalone function, then it is still possible to use the decorator form of ExitStack.callback() to declare the resource cleanup in advance:如果资源清理还没有整齐地绑定到一个独立的函数中,那么仍然可以使用ExitStack.callback()的装饰器形式提前声明资源清理:

from contextlib import ExitStack
with ExitStack() as stack:
@stack.callback
def cleanup_resources():
...
result = perform_operation()
if result:
stack.pop_all()

Due to the way the decorator protocol works, a callback function declared this way cannot take any parameters. 由于decorator协议的工作方式,以这种方式声明的回调函数不能接受任何参数。Instead, any resources to be released must be accessed as closure variables.相反,任何要释放的资源都必须作为闭包变量进行访问。

Using a context manager as a function decorator使用上下文管理器作为函数装饰器

ContextDecorator makes it possible to use a context manager in both an ordinary with statement and also as a function decorator.使得可以在普通的with语句中使用上下文管理器,也可以将其用作函数装饰器。

For example, it is sometimes useful to wrap functions or groups of statements with a logger that can track the time of entry and time of exit. 例如,有时使用记录器包装函数或语句组是有用的,该记录器可以跟踪进入时间和退出时间。Rather than writing both a function decorator and a context manager for the task, inheriting from ContextDecorator provides both capabilities in a single definition: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)

Instances of this class can be used as both a context manager:此类的实例既可以用作上下文管理器:

with track_entry_and_exit('widget loader'):
print('Some time consuming activity goes here')
load_widget()

And also as a function decorator:同时作为函数装饰器:

@track_entry_and_exit('widget loader')
def activity():
print('Some time consuming activity goes here')
load_widget()

Note that there is one additional limitation when using context managers as function decorators: there’s no way to access the return value of __enter__(). 请注意,使用上下文管理器作为函数装饰器时还有一个额外的限制:无法访问__enter__()的返回值。If that value is needed, then it is still necessary to use an explicit with statement.如果需要该值,那么仍然需要使用显式with语句。

See also

PEP 343 - The “with” statement

The specification, background, and examples for the Python with statement.Python with语句的规范、背景和示例。

Single use, reusable and reentrant context managers一次性、可重复使用和可重入的上下文管理器

Most context managers are written in a way that means they can only be used effectively in a with statement once. 大多数上下文管理器的编写方式意味着它们只能在with语句中有效使用一次。These single use context managers must be created afresh each time they’re used - attempting to use them a second time will trigger an exception or otherwise not work correctly.每次使用这些一次性上下文管理器时,都必须重新创建它们——试图再次使用它们将引发异常或无法正常工作。

This common limitation means that it is generally advisable to create context managers directly in the header of the with statement where they are used (as shown in all of the usage examples above).这种常见的限制意味着,通常建议直接在使用上下文管理器的with语句的头中创建上下文管理器(如上面所有的使用示例所示)。

Files are an example of effectively single use context managers, since the first with statement will close the file, preventing any further IO operations using that file object.文件是有效的一次性上下文管理器的一个例子,因为第一个with语句将关闭文件,防止使用该文件对象进行任何进一步的IO操作。

Context managers created using contextmanager() are also single use context managers, and will complain about the underlying generator failing to yield if an attempt is made to use them a second time:使用contextmanager()创建的上下文管理器也是一次性上下文管理器,如果试图再次使用它们,则会抱怨底层生成器无法生成:

>>> 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

Reentrant context managers重新输入上下文管理器

More sophisticated context managers may be “reentrant”. 更复杂的上下文管理器可能是“可重入的”。These context managers can not only be used in multiple with statements, but may also be used inside a with statement that is already using the same context manager.这些上下文管理器不仅可以在多个with语句中使用,还可以在已经使用同一上下文管理器的with语句内部使用。

threading.RLock is an example of a reentrant context manager, as are suppress() and redirect_stdout(). 是可重入上下文管理器的一个示例,suppress()redirect_stdout()也是如此。Here’s a very simple example of reentrant use:下面是一个非常简单的可重入使用示例:

>>> 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

Real world examples of reentrancy are more likely to involve multiple functions calling each other and hence be far more complicated than this example.现实世界中的可重入性示例更有可能涉及多个函数相互调用,因此比本示例复杂得多。

Note also that being reentrant is not the same thing as being thread safe. 还要注意,可重入性与线程安全性是不一样的。redirect_stdout(), for example, is definitely not thread safe, as it makes a global modification to the system state by binding sys.stdout to a different stream.例如,redirect_stdout()肯定不是线程安全的,因为它通过将sys.stdout绑定到不同的流来全局修改系统状态。

Reusable context managers可重用的上下文管理器

Distinct from both single use and reentrant context managers are “reusable” context managers (or, to be completely explicit, “reusable, but not reentrant” context managers, since reentrant context managers are also reusable). 与一次性和可重入上下文管理器不同的是“可重用”上下文管理器(或者,完全明确地说,“可重用但不可重入”上下文管理者,因为可重入的上下文管理器也是可重用的)。These context managers support being used multiple times, but will fail (or otherwise not work correctly) if the specific context manager instance has already been used in a containing with statement.这些上下文管理器支持多次使用,但如果特定的上下文管理器实例已在containing with语句中使用,则会失败(或无法正常工作)。

threading.Lock is an example of a reusable, but not reentrant, context manager (for a reentrant lock, it is necessary to use threading.RLock instead).是一个可重用但不可重入的上下文管理器的示例(对于重入锁,有必要使用threading.RLock)。

Another example of a reusable, but not reentrant, context manager is ExitStack, as it invokes all currently registered callbacks when leaving any with statement, regardless of where those callbacks were added:可重复使用但不可重入的上下文管理器的另一个示例是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

As the output from the example shows, reusing a single stack object across multiple with statements works correctly, but attempting to nest them will cause the stack to be cleared at the end of the innermost with statement, which is unlikely to be desirable behaviour.正如示例的输出所示,在多个with语句中重用单个堆栈对象是正确的,但试图嵌套它们会导致堆栈在最里面的with语句末尾被清除,这不太可能是理想的行为。

Using separate ExitStack instances instead of reusing a single instance avoids that problem:使用单独的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