用Python编写本身使用with语句的上下文管理器

用Python编写本身使用with语句的上下文管理器,python,contextmanager,Python,Contextmanager,我正在尝试编写一个使用其他上下文管理器的上下文管理器,这样客户就不需要知道整个配方,只需要知道我正在演示的接口。我无法使用@contextmanager-如果异常中断,则不会执行yield调用后的代码,因此我需要使用基于类的管理器 下面是一个小脚本示例: from contextlib import contextmanager import pprint d = {} @contextmanager def simple(arg, val): print "enter", arg

我正在尝试编写一个使用其他上下文管理器的上下文管理器,这样客户就不需要知道整个配方,只需要知道我正在演示的接口。我无法使用
@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
{}

有什么方法可以告诉基于类的上下文管理器使用其他上下文管理器包装自己吗?

您所做的问题是,在
\uuuuuu enter\uuuuu
调用中使用
with
时,当您进入包装上下文管理器时,您都会进入并离开包装好的上下文管理器。如果您想编写自己的上下文管理器,在进入包装器时进入包装的上下文管理器,然后在离开时退出它们,那么您必须手动调用包装的上下文管理器的函数。您可能还需要担心异常安全性。

您写道,“我不能使用@contextmanager来完成它-如果您被异常中断,则不会执行yield调用后的代码。”如果您有必须运行的代码,则可以将其放入
try/finally
块中

@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
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!!
输入内部\u cm
在外部
地点A
从外部清洁地退出
最后是外部的
从内部清洁地退出
最后是内部控制模块
输入内部\u cm
在外部
地点B
最后是外部的
最后是内部控制模块
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
文件“C:\Anaconda\lib\site packages\spyderlib\widgets\externalshell\sitecustomize.py”,第540行,在runfile中
execfile(文件名、命名空间)
文件“C:\untitled0.py”,第35行,在
引发异常(“出现某些异常!!”)
异常:发生了一些异常!!

指定Python版本很有用。请原谅这个问题,但为什么要这样做?在我看来,基于类的上下文管理器应该在清除依赖项后最后退出似乎很自然。我的用例是用于测试视图(在webapp中)的登录上下文管理器。登录需要对mock进行几个调用,这些调用通过with语句进行。我希望这个助手登录上下文管理器的用户能够调用登录管理器,而不必知道要模拟什么。外部CMs需要它们的上下文在传递给登录CM的块中保持不变,这样它们就不会在登录的enter方法中过期。这是一个非常复杂的问题。您能否指出/解释asker代码中的问题,以便更快地理解发生了什么?:)@naxa:看问题中最后两个输出示例。问题中的代码生成第一个输出,答案中的代码生成第二个输出(理想的输出)。简而言之:嵌套最多的上下文管理器应该最快退出。