将来自2个类的语句包装在一个类中的python

时间:2018-10-09 21:35:00

标签: python with-statement

我有一个带有with语句的python类和带有with语句的B类。现在它的工作方式如下:

with A() as a:
    with B() as b:
        do_things()

我如何构建一个包装了A类和B类的C类,这样我可以这样称呼它:

 with C() as c:
        do_things()

具有相同的功能

3 个答案:

答案 0 :(得分:2)

我想提出一个替代方案。您可以在同一行上初始化ab

with A() as a, B() as b:
    do_things()

这更加简洁,并减少了深层嵌套代码中的缩进量。

但是,如果您绝对必须使用一个类,则重写__enter____exit__方法:

class C:
    def __enter__(self):
        self._a = A()
        self._b = B()
        return (self._a.__enter__(), self._b.__enter__())

    def __exit__(self ,type, value, traceback):
        # Cleanup code here.
        self._b.__exit__(type, value, traceback)
        self._a.__exit__(type, value, traceback)

然后在这样的上下文管理器中使用C

with C() as (a, b):
    do_things()

如果您不想引用ab,或者不打算对它们进行任何操作,则

with C():
    do_things()

也可以。这应该足以开始,但是请注意,用户在注释中有一些缺点。主要的问题是,如果self._b.__enter__抛出错误,则需要清理self._a.__enter__(这可以使用try-except-finally来完成)。此外,根据所管理的资源,可能需要对某些上下文管理器进行区别对待。

答案 1 :(得分:2)

简短答案:这是可能。但是上下文管理器允许实现某些逻辑,这使得“正确”地实现它变得“棘手”。在下面,您会看到“概念证明”,但我不保证它具有完全相同的行为。因此,我真的建议您使用嵌套的with

此处未涵盖的内容__init____enter__也会引发异常,然后由“外部”上下文管理器处理这些异常。这当然使其相当复杂。基本上,您将需要在__enter__中“构建”堆栈,然后在__enter__中的一个失败的情况下“弹出”堆栈。此处 不涉及这种情况。

我们可以创建一个“复合”上下文管理器:

class C:

    def __init__(self, *ctxs):
        self.ctxs = ctxs

    def __enter__(self):
        return tuple(ctx.__enter__() for ctx in self.ctxs)

    def __exit__(self, self, exception_type, exception_value, traceback):
        for ctx in reversed(self.ctxs):
            try:
                if ctx.__exit__(exception_type, exception_value, traceback):
                    (exception_type, exception_value, traceback) = (None,) * 3
            except Exception as e:
                exception_value = e
                traceback = e.__traceback__
                exception_type = type(e)
        return exception_value is None

__exit__部分很棘手。首先,我们需要按 reverse 顺序退出。但是异常处理更加复杂:如果__exit__通过返回“真实”值使异常静音,则我们应该将(None, None, None)传递为(exception_type, exeption_value, traceback),但是可能会出现问题另一方面,__exit__本身会触发异常,从而引入新的异常。

然后我们可以使用上下文处理器,例如:

with C(A(), B()) as (a,b):
    # ...
    pass

因此,上述内容允许为任意数量的“子上下文管理器”实现上下文管理器。我们可以对此子类化以生成特定的子类,例如:

class ContextAB(C):

    def __init__(self):
        super(ContextAB, self).__init__(A(), B())

,然后将其用作:

with ContextAB() as (a, b):
    # ...
    pass

但是长话短说:使用嵌套的with语句。它还使这里发生的事情更加明确。现在,C封装了各种逻辑,最好将其明确化。如果输入 B失败,则应导致由__exit__的{​​{1}}处理的异常,以此类推。这使得获取“细节”完全等同于A语句的语义。

答案 2 :(得分:1)

如果您这样做的目的是减少缩进或合并with语句,则无需这样做。你可以做

with A() as a, B() as b:
    ...

在一行上输入多个上下文管理器。

如果您的C还有其他原因,则在创建和/或输入另一个上下文管理器后一个上下文管理器发生故障的情况下,需要谨慎处理。 contextlib.ExitStack可以帮助您稳健地实现这一目标;如果您仍在使用Python 2,则可以使用contextlib2.ExitStack

import contextlib

class C(object):
    def __enter__(self):
        with contextlib.ExitStack() as stack:
            stack.enter_context(A())
            stack.enter_context(B())
            self._stack = stack.pop_all()
        return self
    def __exit__(self, exc_type, exc_value, exc_tb):
        return self._stack.__exit__(exc_type, exc_value, exc_tb)