我有一个带有with
语句的python类和带有with
语句的B类。现在它的工作方式如下:
with A() as a:
with B() as b:
do_things()
我如何构建一个包装了A类和B类的C类,这样我可以这样称呼它:
with C() as c:
do_things()
具有相同的功能
答案 0 :(得分:2)
我想提出一个替代方案。您可以在同一行上初始化a
和b
:
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()
如果您不想引用a
和b
,或者不打算对它们进行任何操作,则
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)