python 中的嵌套上下文管理器,具有列表理解功能

nested context managers in python with list comprehension

假设我有两个不纯函数 - f 和 j。 j 生成一个批次中的项目列表,f 生成一个批次列表。他们俩都有一些清理工作要做。

我需要做的是为客户端代码提供扁平化的项目列表,同时对 f 和 j 进行清理。一种方法是使用带有 for 循环的生成器在 yield 之后进行清理,但我不喜欢这种方式,因为在这种情况下清理存在并不明确。

所以我找到了一种使用包装函数(在此代码中称为 dumb)的方法

from contextlib import contextmanager
from split import chop
from itertools import chain
from functools import wraps

xs = list(chop(3, xrange(9)))


def dumb(manager):
    @wraps(manager)
    def g(*args, **kwargs):
        with manager(*args, **kwargs) as something:
            return something
    return g

@dumb
@contextmanager
def j(index):
    print('before j')
    yield xs[index]
    print('after j')

@contextmanager
def f():
    print('before f')
    yield chain.from_iterable(j(i) for i in xrange(len(xs)))

    print('after f')

with f() as ns:
    for x in ns:
        print(x)

打印

before f
before j
after j
0
1
2
before j
after j
3
4
5
before j
after j
6
7
8
after f

编辑1。它实际上不起作用,因为它在实际消耗值之前在 j 之前和之后执行。

我想说此时你已经不再需要 @contextmanager 装饰器了,是时候编写你自己的上下文管理器了 class.

from contextlib import contextmanager

xs = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]

@contextmanager
def j(index):
    """Same as before. This is a simple context manager."""
    print("j init")
    yield xs[index]
    print("j cleanup")

def _real_f(the_xs):
    """Mostly the same as before. However, any share state created in the 
    init phase should be passed as an argument. As an example I pass in xs."""
    for i in range(len(the_xs)):
        # can now use explicit with statements for j
        with j(i) as it:
            for item in it:
                yield item

class f(object):
    """f is now replaced by a class that can create contexts for the real
    f to work with"""
    def __enter__(self):
        """Init phase. 
        State required by the real f should be passed as an argument (I
        pass in xs)."""
        print("f init")
        return _real_f(xs)

    def __exit__(self, exception_type, exception, traceback):
        """Clean up phase. 
        Called at end of block. With any details of any exception that
        may have occured in the with block. Return a truthful value to 
        swallow the exception. Can raise your own exception if there is a 
        problem in the clean up phase."""
        print("f clean up")

with f() as it:
    for item in it:
        print(item)