Python中的上下文管理器

contextlib — Context Manager Utilities

contextlib - 上下文管理器套件

Purpose: Utilities for creating and working with context managers.
The contextlib module contains utilities for working with context managers and the with statement.
目的: 用于创建和使用上下文管理器的套件。contextlib模块包含用于使用上下文管理器即with语句的套件。

Context Manager API
上下文管理器API

A context manager is responsible for a resource within a code block, possibly creating it when the block is entered and then cleaning it up after the block is exited. For example, files support the context manager API to make it easy to ensure they are closed after all reading or writing is done.
上下文管理器主要用于处理一个代码块中的资源:可能在进入代码块时创建,然后在代码块执行结束后清理。例如:如下所示的代码,使用支持上下文管理器API的文件打开方式将很轻易的确保在读写完成之后,文件都将被关闭。

contextlib_file.py

with open('/tmp/pymotw.txt', 'wt') as f:
    f.write('contents go here')

file is automatically closed

文件自动被关闭

A context manager is enabled by the with statement, and the API involves two methods. The enter() method is run when execution flow enters the code block inside the with. It returns an object to be used within the context. When execution flow leaves the with block, the exit() method of the context manager is called to clean up any resources being used.
上下文管理器可以由with语句开启,他的API包含两个方法:当程序进入with语句块时,就运行 enter()方法。所返回的对象可以在上下文中使用。当执行语句流要离开with语句块时,调用上下文管理器的 exit()方法将清理所使用的全部资源。

contextlib_api.py

class Context:

    def __init__(self):
        print('__init__()')

    def __enter__(self):
        print('__enter__()')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__()')

with Context():
    print('Doing work in the context')

Combining a context manager and the with statement is a more compact way of writing a try:finally block, since the context manager’s exit() method is always called, even if an exception is raised.
将上下文管理器和with语句结合起来使用,比编写try:finally语句块显得更简洁,这是因为上下文管理器的 exit()方法会自动调用,甚至在出现异常的情况下。

  • python3 contextlib_api.py
__init__()
__enter__()
Doing work in the context
__exit__()

The enter() method can return any object to be associated with a name specified in the as clause of the with statement. In this example, the Context returns an object that uses the open context.
enter()方法能够返回任何在with语句中的as子句所指定名称相关联的对象。 在本示例中,Context类返回一个使用打开上下文的对象。

contextlib_api_other_object.py

class WithinContext:

    def __init__(self, context):
        print('WithinContext.__init__({})'.format(context))

    def do_something(self):
        print('WithinContext.do_something()')

    def __del__(self):
        print('WithinContext.__del__')


class Context:

    def __init__(self):
        print('Context.__init__()')

    def __enter__(self):
        print('Context.__enter__()')
        return WithinContext(self)

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('Context.__exit__()')

with Context() as c:
    c.do_something()

The value associated with the variable c is the object returned by enter(), which is not necessarily the Context instance created in the with statement.
与变量c相关联的值是由enter()所返回的对象,并不是必须在with语句中创建的Context实例。

$ python3 contextlib_api_other_object.py

Context.init()
Context.enter()
WithinContext.init(<main.Context object at 0x1007b1c50>)
WithinContext.do_something()
Context.exit()
WithinContext.del

The exit() method receives arguments containing details of any exception raised in the with block.
exit()方法收到的参数,包含with语句块中所抛出的任何异常信息的细节。

contextlib_api_error.py

class Context:

    def __init__(self, handle_error):
        print('__init__({})'.format(handle_error))
        self.handle_error = handle_error

    def __enter__(self):
        print('__enter__()')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__()')
        print('  exc_type =', exc_type)
        print('  exc_val  =', exc_val)
        print('  exc_tb   =', exc_tb)
        return self.handle_error

with Context(True):
    raise RuntimeError('error message handled')

print()

with Context(False):
    raise RuntimeError('error message propagated')

If the context manager can handle the exception, exit() should return a true value to indicate that the exception does not need to be propagated. Returning false causes the exception to be re-raised after exit() returns.
如果上下文管理器能够处理异常,exit()将返回一个true,指明异常不需要被传播。如果返回false将导致异常在 exit()在返回之后再次抛出。

$ python3 contextlib_api_error.py

init(True)
enter()
exit()
exc_type = <class 'RuntimeError'>
exc_val = error message handled
exc_tb = <traceback object at 0x10115cc88>

init(False)
enter()
exit()
exc_type = <class 'RuntimeError'>
exc_val = error message propagated
exc_tb = <traceback object at 0x10115cc88>
Traceback (most recent call last):
File "contextlib_api_error.py", line 33, in <module>
raise RuntimeError('error message propagated')
RuntimeError: error message propagated

Context Managers as Function Decorators

上下文管理器作为函数装饰器

The class ContextDecorator adds support to regular context manager classes to let them be used as function decorators as well as context managers.
ContextDecorator类增加了对于一般的上下文管理器类的支持,允许它们既作为函数装饰器,又作为上下文管理器使用。

contextlib_decorator.py

import contextlib

class Context(contextlib.ContextDecorator):

    def __init__(self, how_used):
        self.how_used = how_used
        print('__init__({})'.format(how_used))

    def __enter__(self):
        print('__enter__({})'.format(self.how_used))
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__({})'.format(self.how_used))


@Context('as decorator')
def func(message):
    print(message)

print()
with Context('as context manager'):
    print('Doing work in the context')

print()
func('Doing work in the wrapped function')

One difference with using the context manager as a decorator is that the value returned by enter() is not available inside the function being decorated, unlike when using with and as. Arguments passed to the decorated function are available in the usual way.
使用上下文管理器作为装饰器的一个区别在于,通过 enter()返回的值并不存在于被装饰的函数之中,这与使用with和as不同。传入装饰函数的参数与往常一样存在。

$ python3 contextlib_decorator.py

init(as decorator)

init(as context manager)
enter(as context manager)
Doing work in the context
exit(as context manager)

enter(as decorator)
Doing work in the wrapped function
exit(as decorator)
From Generator to Context Manager

Creating context managers the traditional way, by writing a class with enter() and exit() methods, is not difficult. But sometimes writing everything out fully is extra overhead for a trivial bit of context. In those sorts of situations, use the contextmanager() decorator to convert a generator function into a context manager.
创建上下文管理器的传统方法,就是通过在类中编写 enter() 和 exit(),这并不困难。但是有时为了一点并不重要的上下文管理,就编写全部完整的内容,显得毫无必要。在这类情况下,使用contextmanager()装饰器,将一个生成器函数转换为一个上下文管理器函数。

contextlib_contextmanager.py

import contextlib

@contextlib.contextmanager
def make_context():
    print('  entering')
    try:
        yield {}
    except RuntimeError as err:
        print('  ERROR:', err)
    finally:
        print('  exiting')

print('Normal:')
with make_context() as value:
    print('  inside with statement:', value)

print('\nHandled error:')
with make_context() as value:
    raise RuntimeError('showing example of handling an error')

print('\nUnhandled error:')
with make_context() as value:
    raise ValueError('this exception is not handled')

The generator should initialize the context, yield exactly one time, then clean up the context. The value yielded, if any, is bound to the variable in the as clause of the with statement. Exceptions from within the with block are re-raised inside the generator, so they can be handled there.
生成器函数将初始化上下文管理器函数,仅仅使用yield一次,然后清理上下文。如果有任何被yield的值,它们都被限定与with语句的as子句的变量之中。来自于with语句块的异常会在生成器内部重新抛出,因此,它们可以在此被处理。

$ python3 contextlib_contextmanager.py

Normal:
  entering
  inside with statement: {}
  exiting

Handled error:
  entering
  ERROR: showing example of handling an error
  exiting

Unhandled error:
  entering
  exiting
Traceback (most recent call last):
  File "contextlib_contextmanager.py", line 32, in <module>
    raise ValueError('this exception is not handled')
ValueError: this exception is not handled

The context manager returned by contextmanager() is derived from ContextDecorator, so it also works as a function decorator.
由contextmanager()返回的上下文管理器是由ContextDecorator衍生出的,因此它以函数装饰器的方式工作。

contextlib_contextmanager_decorator.py

import contextlib

@contextlib.contextmanager
def make_context():
    print('  entering')
    try:
        # Yield control, but not a value, because any value
        # yielded is not available when the context manager
        # is used as a decorator.
        yield
    except RuntimeError as err:
        print('  ERROR:', err)
    finally:
        print('  exiting')


@make_context()
def normal():
    print('  inside with statement')


@make_context()
def throw_error(err):
    raise err


print('Normal:')
normal()

print('\nHandled error:')
throw_error(RuntimeError('showing example of handling an error'))

print('\nUnhandled error:')
throw_error(ValueError('this exception is not handled'))

As in the ContextDecorator example above, when the context manager is used as a decorator the value yielded by the generator is not available inside the function being decorated. Arguments passed to the decorated function are still available, as demonstrated by throw_error() in this example.
正如以上ContextDecorator示例所展示,当context管理器当作装饰器使用时,通过生成器yield的值并不存在于被装饰的函数之中。传递至装饰器函数的参数仍然有效,正如在本示例中通过throw_error()所展示的。

$ python3 contextlib_contextmanager_decorator.py

Normal:
  entering
  inside with statement
  exiting

Handled error:
  entering
  ERROR: showing example of handling an error
  exiting

Unhandled error:
  entering
  exiting
Traceback (most recent call last):
  File "contextlib_contextmanager_decorator.py", line 43, in
<module>
    throw_error(ValueError('this exception is not handled'))
  File ".../lib/python3.5/contextlib.py", line 30, in inner
    return func(\*args, \*\*kwds)
  File "contextlib_contextmanager_decorator.py", line 33, in
throw_error
    raise err
ValueError: this exception is not handled

Closing Open Handles
关闭打开操作

The file class supports the context manager API directly, but some other objects that represent open handles do not. The example given in the standard library documentation for contextlib is the object returned from urllib.urlopen(). There are other legacy classes that use a close() method but do not support the context manager API. To ensure that a handle is closed, use closing() to create a context manager for it.
file类能够直接支持上下文管理器,但是一些表示打开文件操作的其他对象并不支持。本示例由标准库contextlib的文档所提供,是urllib.urlopen()所返回的对象。还有其他的遗留代码的类,使用close()方法,但是并不支持上下文管理器API。想要确保句柄是关闭的,为其使用closing()来创建一个上下文管理器。

contextlib_closing.py

import contextlib


class Door:

    def __init__(self):
        print('  __init__()')
        self.status = 'open'

    def close(self):
        print('  close()')
        self.status = 'closed'

print('Normal Example:')
with contextlib.closing(Door()) as door:
    print('  inside with statement: {}'.format(door.status))
print('  outside with statement: {}'.format(door.status))

print('\nError handling example:')
try:
    with contextlib.closing(Door()) as door:
        print('  raising from inside with statement')
        raise RuntimeError('error message')
except Exception as err:
    print('  Had an error:', err)

The handle is closed whether there is an error in the with block or not.
句柄都将被关闭,无论是否在with语句中包含错误。

$ python3 contextlib_closing.py

Normal Example:
init()
inside with statement: open
close()
outside with statement: closed

Error handling example:
init()
raising from inside with statement
close()
Had an error: error message
Ignoring Exceptions

It is frequently useful to ignore exceptions raised by libraries, because the error indicates that the desired state has already been achieved, or it can otherwise be ignored. The most common way to ignore exceptions is with a try:except statement with only a pass statement in the except block.
通常情况下忽略由库函数抛出的异常信息是很有必要的,因为这些错误表明了所期望的状态已经达到,或者某种情况下可以被忽略。忽略异常最常用的方法就是使用try:except语句,而且在except语句块中只放入一个pass语句。

contextlib_ignore_error.py

import contextlib


class NonFatalError(Exception):
    pass


def non_idempotent_operation():
    raise NonFatalError(
        'The operation failed because of existing state'
    )


try:
    print('trying non-idempotent operation')
    non_idempotent_operation()
    print('succeeded!')
except NonFatalError:
    pass

print('done')

In this case, the operation fails and the error is ignored.
在这种情况下,操作失败,并且忽略错误。

$ python3 contextlib_ignore_error.py

trying non-idempotent operation

done

The try:except form can be replaced with contextlib.suppress() to more explicitly suppress a class of exceptions happening anywhere in the with block.
try:except形式可以使用contextlib.suppress()替换,这样就可以更显式的抑制语句块中出现在任何位置的异常。

contextlib_suppress.py

import contextlib


class NonFatalError(Exception):
    pass


def non_idempotent_operation():
    raise NonFatalError(
        'The operation failed because of existing state'
    )


with contextlib.suppress(NonFatalError):
    print('trying non-idempotent operation')
    non_idempotent_operation()
    print('succeeded!')

print('done')

In this updated version, the exception is discarded entirely.
在这个更新的版本中,异常已经完全被抛弃了。

$ python3 contextlib_suppress.py

trying non-idempotent operation
done

Redirecting Output Streams
重定向输出流

Poorly designed library code may write directly to sys.stdout or sys.stderr, without providing arguments to configure different output destinations. The redirect_stdout() and redirect_stderr() context managers can be used to capture output from functions like this, for which the source cannot be changed to accept a new output argument.
设计不好的库代码可能回直接将输出写入sys.stdout或者sys.stderr,而没有提供参数来配置不同的输出方式。redirect_stdout()和redirect_stderr()上下文管理器能够被用于捕获类似于这样的函数输出:输入源无法被改变来接受一个新的输出参数。

contextlib_redirect.py

from contextlib import redirect_stdout, redirect_stderr
import io
import sys


def misbehaving_function(a):
    sys.stdout.write('(stdout) A: {!r}\n'.format(a))
    sys.stderr.write('(stderr) A: {!r}\n'.format(a))


capture = io.StringIO()
with redirect_stdout(capture), redirect_stderr(capture):
    misbehaving_function(5)

print(capture.getvalue())

In this example, misbehaving_function() writes to both stdout and stderr, but the two context managers send that output to the same io.StringIO instance where it is saved to be used later.
在本示例中,misbehaving_function()同时写入stdout和stderr,但是两个上下文管理器将输出发送至同一个io.StringIO实例,保存以备后续使用。

$ python3 contextlib_redirect.py

(stdout) A: 5
(stderr) A: 5

Note
注意

Both redirect_stdout() and redirect_stderr() modify global state by replacing objects in the sys module, and should be used with care. The functions are not thread-safe, and may interfere with other operations that expect the standard output streams to be attached to terminal devices.
redirect_stdout()和redirect_stderr()函数通过覆盖sys模块中的对象来修改全局对象,使用时一定要很小心。而且这个函数不是线程安全,而且可能影响其他期望将标准输出流附加到终端的操作。

Dynamic Context Manager Stacks
动态上下文管理器栈

Most context managers operate on one object at a time, such as a single file or database handle. In these cases, the object is known in advance and the code using the context manager can be built around that one object. In other cases, a program may need to create an unknown number of objects in a context, while wanting all of them to be cleaned up when control flow exits the context. ExitStack was created to handle these more dynamic cases.
大多数上下文管理器一次操作一个对象,例如一个文件或者一个数据库句柄。在这些情况下,对象是预先知道的,而且使用上下文管理器的代码可以被用于基于一个对象构建;在其他情况下,程序可能需要在上下文中创建一定未知数量的对象,当程序控制流退出上下文时,就将所有的这些对象清理。创建ExitStack处理这些动态类型。

An ExitStack instance maintains a stack data structure of cleanup callbacks. The callbacks are populated explicitly within the context, and any registered callbacks are called in the reverse order when control flow exits the context. The result is like having multple nested with statements, except they are established dynamically.
ExitStack示例维护了一个清理回调函数当数据结构栈。回调函数显式的注入上下文,然后当控制流退出上下文时,所有已注册的回调函数依次按照逆序调用。结果就像是有多个嵌入的语句,除非他们是自动创建。

Stacking Context Managers
上下文管理器栈

There are several ways to populate the ExitStack. This example uses enter_context() to add a new context manager to the stack.
由很多种方法为ExitStack填入数据,本示例使用enter_context()方法为栈添加一个新的上下文管理器。

contextlib_exitstack_enter_context.py

import contextlib


@contextlib.contextmanager
def make_context(i):
    print('{} entering'.format(i))
    yield {}
    print('{} exiting'.format(i))


def variable_stack(n, msg):
    with contextlib.ExitStack() as stack:
        for i in range(n):
            stack.enter_context(make_context(i))
        print(msg)


variable_stack(2, 'inside context')

enter_context() first calls enter() on the context manager, and then registers its exit() method as a callback to be invoked as the stack is undone.
enter_context()首先在上下文管理器上调用 enter(),然后将其注册到 exit() 方法上,作为一旦栈被撤销,就调用的回调函数。

$ python3 contextlib_exitstack_enter_context.py

0 entering
1 entering
inside context
1 exiting
0 exiting

The context managers given to ExitStack are treated as though they are in a series of nested with statements. Errors that happen anywhere within the context propagate through the normal error handling of the context managers. These context manager classes illustrate the way errors propagate.
提供给ExitStack的上下文管理器,嵌套在一系列的with语句之中。其中上下文中任何位置发生的错误,都将通过正常错误处理的上下文管理器传播。这些上下文管理器类展示了传播错误的方式。

contextlib_context_managers.py

import contextlib

class Tracker:
    "Base class for noisy context managers."

    def __init__(self, i):
        self.i = i

    def msg(self, s):
        print('  {}({}): {}'.format(
            self.__class__.__name__, self.i, s))

    def __enter__(self):
        self.msg('entering')


class HandleError(Tracker):
    "If an exception is received, treat it as handled."

    def __exit__(self, \*exc_details):
        received_exc = exc_details[1] is not None
        if received_exc:
            self.msg('handling exception {!r}'.format(
                exc_details[1]))
        self.msg('exiting {}'.format(received_exc))
        # Return Boolean value indicating whether the exception
        # was handled.
        return received_exc


class PassError(Tracker):
    "If an exception is received, propagate it."

    def __exit__(self, \*exc_details):
        received_exc = exc_details[1] is not None
        if received_exc:
            self.msg('passing exception {!r}'.format(
                exc_details[1]))
        self.msg('exiting')
        # Return False, indicating any exception was not handled.
        return False


class ErrorOnExit(Tracker):
    "Cause an exception."

    def __exit__(self, \*exc_details):
        self.msg('throwing error')
        raise RuntimeError('from {}'.format(self.i))


class ErrorOnEnter(Tracker):
    "Cause an exception."

    def __enter__(self):
        self.msg('throwing error on enter')
        raise RuntimeError('from {}'.format(self.i))

    def __exit__(self, \*exc_info):
        self.msg('exiting')

The examples using these classes are based around variable_stack(), which uses the context managers passed to construct an ExitStack, building up the overall context one by one. The examples below pass different context managers to explore the error handling behavior. First, the normal case of no exceptions.
本示例使用这些类都是基于variable_stack()方法,该方法使用传入上下文管理器构建一个ExitStack,逐步构建完整的上下文。以下示例传递不同的上下文管理器,来测试错误处理行为。首先传入没有异常的正常情况:

print('No errors:')
variable_stack([
    HandleError(1),
    PassError(2),
])

Then, an example of handling exceptions within the context managers at the end of the stack, in which all of the open contexts are closed as the stack is unwound.
然后,使用上下文管理器处理栈末尾异常的示例,在该栈中,一旦栈展开,所有打开的上下文管理器都关闭。

print('\nError at the end of the context stack:')
variable_stack([
    HandleError(1),
    HandleError(2),
    ErrorOnExit(3),
])

Next, an example of handling exceptions within the context managers in the middle of the stack, in which the error does not occur until some contexts are already closed, so those contexts do not see the error.
下一步,使用上下文管理器处理栈中间异常的示例,在该栈中,直到上下文管理器已经关闭,栈中的错误都不会产生,因此,上下文管理器将看不到这个错误。

print('\nError in the middle of the context stack:')
variable_stack([
    HandleError(1),
    PassError(2),
    ErrorOnExit(3),
    HandleError(4),
])

Finally, an example of the exception remaining unhandled and propagating up to the calling code.
最后,遗留的异常代码未处理,并且传播到调用代码。

try:
    print('\nError ignored:')
    variable_stack([
        PassError(1),
        ErrorOnExit(2),
    ])
except RuntimeError:
    print('error handled outside of context')

If any context manager in the stack receives an exception and returns a True value, it prevents that exception from propagating up to any other context managers.
如果栈中的任何上下文管理器收到异常并且返回True,就阻止了向其他上下文管理器传播异常。

$ python3 contextlib_exitstack_enter_context_errors.py

No errors:
HandleError(1): entering
PassError(2): entering
PassError(2): exiting
HandleError(1): exiting False
outside of stack, any errors were handled

Error at the end of the context stack:
HandleError(1): entering
HandleError(2): entering
ErrorOnExit(3): entering
ErrorOnExit(3): throwing error
HandleError(2): handling exception RuntimeError('from 3',)
HandleError(2): exiting True
HandleError(1): exiting False
outside of stack, any errors were handled

Error in the middle of the context stack:
HandleError(1): entering
PassError(2): entering
ErrorOnExit(3): entering
HandleError(4): entering
HandleError(4): exiting False
ErrorOnExit(3): throwing error
PassError(2): passing exception RuntimeError('from 3',)
PassError(2): exiting
HandleError(1): handling exception RuntimeError('from 3',)
HandleError(1): exiting True
outside of stack, any errors were handled

Error ignored:
PassError(1): entering
ErrorOnExit(2): entering
ErrorOnExit(2): throwing error
PassError(1): passing exception RuntimeError('from 2',)
PassError(1): exiting
error handled outside of context
Arbitrary Context Callbacks

ExitStack also supports arbitrary callbacks for closing a context, making it easy to clean up resources that are not controlled via a context manager.
为了关闭一个上下文管理器,ExitStack 也支持任何形式的回调,使不被上下文管理器控制的资源清理变的简单。

contextlib_exitstack_callbacks.py

import contextlib


def callback(\*args, \*\*kwds):
    print('closing callback({}, {})'.format(args, kwds))


with contextlib.ExitStack() as stack:
    stack.callback(callback, 'arg1', 'arg2')
    stack.callback(callback, arg3='val3')

Just as with the exit() methods of full context managers, the callbacks are invoked in the reverse order that they are registered.
正如完整上下文管理器的 exit() 方法,回调函数以注册的逆序被调用。

$ python3 contextlib_exitstack_callbacks.py

closing callback((), {'arg3': 'val3'})
closing callback(('arg1', 'arg2'), {})

The callbacks are invoked regardless of whether an error occurred, and they are not given any information about whether an error occurred. Their return value is ignored.
无论是否发生错误,都会调用回调函数,而且不会提供任何关于是否有错误发生的信息,直接忽略返回值。

contextlib_exitstack_callbacks_error.py

import contextlib


def callback(\*args, \*\*kwds):
    print('closing callback({}, {})'.format(args, kwds))


try:
    with contextlib.ExitStack() as stack:
        stack.callback(callback, 'arg1', 'arg2')
        stack.callback(callback, arg3='val3')
        raise RuntimeError('thrown error')
except RuntimeError as err:
    print('ERROR: {}'.format(err))

Because they do not have access to the error, callbacks are unable to suppress exceptions from propagating through the rest of the stack of context managers.
由于并未接触到错误,回调函数就不能抑制来自于通过上下文管理器其他栈传播的异常。

$ python3 contextlib_exitstack_callbacks_error.py

closing callback((), {'arg3': 'val3'})
closing callback(('arg1', 'arg2'), {})
ERROR: thrown error

Callbacks make a convenient way to clearly define cleanup logic without the overhead of creating a new context manager class. To improve code readability, that logic can be encapsulated in an inline function, and callback() can be used as a decorator.
回调函数使定义清理函数逻辑非常清晰并且方便,而且避免了创建一个新的上下文管理器类的开销。想要证明代码的可读性,可以将逻辑封装在一个内嵌函数中,将回调函数作为装饰器。

contextlib_exitstack_callbacks_decorator.py

import contextlib

with contextlib.ExitStack() as stack:

    @stack.callback
    def inline_cleanup():
        print('inline_cleanup()')
        print('local_resource = {!r}'.format(local_resource))

    local_resource = 'resource created in context'
    print('within the context')

There is no way to specify the arguments for functions registered using the decorator form of callback(). However, if the cleanup callback is defined inline, scope rules give it access to variables defined in the calling code.
对于使用装饰器形式的callback()函数注册的函数没有办法指定参数。然而,如果清理回调函数以内嵌的方式定义,作用域管理将能够给予访问在调用代码中定义变量的权力。

$ python3 contextlib_exitstack_callbacks_decorator.py

within the context
inline_cleanup()
local_resource = 'resource created in context'
Partial Stacks

Sometimes when building complex contexts it is useful to be able to abort an operation if the context cannot be completely constructed, but to delay the cleanup of all resources until a later time if they can all be set up properly. For example, if an operation needs several long-lived network connections, it may be best to not start the operation if one connection fails. However, if all of the connections can be opened they need to stay open longer than the duration of a single context manager. The pop_all() method of ExitStack can be used in this scenario.
有时候,当构建复杂上下文时,如果上下文没有完整的构建,退出机制就非常有用。但是如果能够全部恰当的配置好,直到后续某个时刻再清理全部的资源。例如:如果操作需要需要一些长时间的网络连接,一旦某一个连接失败,就最好不要开始操作。然而,如果所有的连接都能打开,那么连接就需要的打开时间比单个上下文管理器的生命周期要长。ExitStack的pop_all()方法能够在这样的场景下使用。

pop_all() clears all of the context managers and callbacks from the stack on which it is called, and returns a new stack pre-populated with those same context managers and callbacks. The close() method of the new stack can be invoked later, after the original stack is gone, to clean up the resources.
pop_all()函数清理了栈中所调用的所有上下文管理器和回调函数,然后返回一个新的栈,栈中预先填入了这些相同的上下文管理器和回调函数。新栈的close()方法能够后续被调用,初始的栈被清空后,就将回收所有的资源。

contextlib_exitstack_pop_all.py

import contextlib

from contextlib_context_managers import *


def variable_stack(contexts):
    with contextlib.ExitStack() as stack:
        for c in contexts:
            stack.enter_context(c)
        # Return the close() method of a new stack as a clean-up
        # function.
        return stack.pop_all().close
    # Explicitly return None, indicating that the ExitStack could
    # not be initialized cleanly but that cleanup has already
    # occurred.
    return None


print('No errors:')
cleaner = variable_stack([
    HandleError(1),
    HandleError(2),
])
cleaner()

print('\nHandled error building context manager stack:')
try:
    cleaner = variable_stack([
        HandleError(1),
        ErrorOnEnter(2),
    ])
except RuntimeError as err:
    print('caught error {}'.format(err))
else:
    if cleaner is not None:
        cleaner()
    else:
        print('no cleaner returned')

print('\nUnhandled error building context manager stack:')
try:
    cleaner = variable_stack([
        PassError(1),
        ErrorOnEnter(2),
    ])
except RuntimeError as err:
    print('caught error {}'.format(err))
else:
    if cleaner is not None:
        cleaner()
    else:
        print('no cleaner returned')

This example uses the same context manager classes defined earlier, with the difference that ErrorOnEnter produces an error on enter() instead of exit(). Inside variable_stack(), if all of the contexts are entered without error then the close() method of a new ExitStack is returned. If a handled error occurs, variable_stack() returns None to indicate that the cleanup work is already done. And if an unhandled error occurs, the partial stack is cleaned up and the error is propagated.
本示例使用之前定义的相同的上下文管理器类,使用不同的 ErrorOnEnter 生成一个在 enter() 的错误,而不是 exit()。在variable_stack()内部,如果进入所有的上下文管理器,而且没有错误,就返回新的ExitStack中的close()方法。如果产生错误,variable_stack()就返回None,表明清理工作已经完成。如果有如何未处理的错误发生,栈的一部分就被清理,并且将错误抛出

$ python3 contextlib_exitstack_pop_all.py

No errors:
HandleError(1): entering
HandleError(2): entering
HandleError(2): exiting False
HandleError(1): exiting False

Handled error building context manager stack:
HandleError(1): entering
ErrorOnEnter(2): throwing error on enter
HandleError(1): handling exception RuntimeError('from 2',)
HandleError(1): exiting True
no cleaner returned

Unhandled error building context manager stack:
PassError(1): entering
ErrorOnEnter(2): throwing error on enter
PassError(1): passing exception RuntimeError('from 2',)
PassError(1): exiting
caught error from 2

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容

  • 之前只知道with...as语句可以作用于一些资源文件的操作,比如filelike对象,可以使它打开后,即使异常也...
    伍只蚊阅读 196评论 1 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • **2014真题Directions:Read the following text. Choose the be...
    又是夜半惊坐起阅读 9,389评论 0 23
  • 每个人生下来都是独一无二,直至离开这个世界,我仍然希望大家都是原创。 从小,我们被灌输一个观念,要做一个好孩子,好...
    篮球发烧友圈阅读 455评论 0 0
  • 一 今天是农历七月十四,中元节,也是俗称的鬼节。在我们那,相传每年七月初鬼门关要打开,让各种没有投胎转世的鬼魂回家...
    强说愁阅读 148评论 0 1