22

我正在尝试编写一个使用其他上下文管理器的上下文管理器,因此客户不需要知道整个配方,只需要知道我正在呈现的界面。我不能这样做@contextmanager——如果你被异常打断,调用后的代码yield不会被执行,所以我需要使用基于类的管理器。

这是一个小示例脚本:

from contextlib import contextmanager
import pprint

d = {}

@contextmanager
def simple(arg, val):
    print "enter", arg
    d[arg] = val
    yield
    print "exit", arg
    del d[arg]

class compl(object):
    def __init__(self, arg, val):
        self.arg=arg
        self.val=val

    def __enter__(self):
        with simple("one",1):
            with simple("two",2):
                print "enter complex", self.arg
                d[self.arg] = self.val

    def __exit__(self,*args):
        print "exit complex", self.arg
        del d[self.arg]

print "before"
print d
print ""

with compl("three",3):
    print d
    print ""

print "after"
print d
print ""

输出如下:

before
{}

enter one
enter two
enter complex three
exit two
exit one
{'three': 3}

exit complex three
after
{}

我希望它输出这个:

before
{}

enter one
enter two
enter complex three
{'one': 1, 'three': 3, 'two': 2}

exit complex three
exit two
exit one
after
{}

有没有办法告诉基于类的上下文管理器将自己与其他上下文管理器一起包装?

4

4 回答 4

21
@contextmanager
def compl(arg, val):
    with simple("one",1):
        with simple("two",2):
            print "enter complex", arg 
            try:
                d[arg] = val
                yield
            finally:
                del d[arg]
                print "exit complex", arg
于 2012-07-11T20:58:16.133 回答
8

你写道,“我不能使用@contextmanager 来做到这一点——如果你被异常打断,yield 调用之后的代码不会被执行。” 如果你有必须运行的代码,你可以把它放在一个try/finally块中。

import contextlib

@contextlib.contextmanager
def internal_cm():
    try:
        print "Entering internal_cm"
        yield None
        print "Exiting cleanly from internal_cm"
    finally:
        print "Finally internal_cm"

@contextlib.contextmanager
def external_cm():
    with internal_cm() as c:
        try:
            print "In external_cm_f"
            yield [c]
            print "Exiting cleanly from external_cm_f"
        finally:
            print "Finally external_cm_f"

if "__main__" == __name__:
    with external_cm() as foo1:
        print "Location A"
    print
    with external_cm() as foo2:
        print "Location B"
        raise Exception("Some exception occurs!!")

输出:

Entering internal_cm
In external_cm_f
Location A
Exiting cleanly from external_cm_f
Finally external_cm_f
Exiting cleanly from internal_cm
Finally internal_cm

Entering internal_cm
In external_cm_f
Location B
Finally external_cm_f
Finally internal_cm
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Anaconda\lib\site-packages\spyderlib\widgets\externalshell\sitecustomize.py", line 540, in runfile
    execfile(filename, namespace)
  File "C:\untitled0.py", line 35, in <module>
    raise Exception("Some exception occurs!!")
Exception: Some exception occurs!!
于 2015-02-09T00:51:42.493 回答
2

您正在做的事情的问题在于,with在您的__enter__调用中使用时,当您进入包装上下文管理器时,您既进入又离开包装的上下文管理器。如果您想编写自己的上下文管理器,当您进入包装器时进入包装的上下文管理器,然后在您离开时退出它们,您必须手动调用包装的上下文管理器的函数。您可能还需要担心异常安全性。

于 2012-07-11T20:59:14.810 回答
2

如果您根据问题的措辞看到这个答案,这里是解决方案的摘要

@contextmanager
def your_custom_context_manager():
    with open(...) as f:
       # do your thing here, e.g. have an `if` statement, or another `with` statement
       yield f  # this is where your new context manager will start from


with your_custom_context_manager() as f:
    # do your stuff
于 2021-10-01T14:49:27.597 回答