是否可以访问封闭的上下文管理器?

Nar*_*lei 15 python contextmanager

使用with语句基本上有三种方法:

使用现有的上下文管理器:

with manager:
    pass
Run Code Online (Sandbox Code Playgroud)

创建上下文管理器并将其结果绑定到变量:

with Manager() as result:
    pass
Run Code Online (Sandbox Code Playgroud)

创建上下文管理器并丢弃其返回值:

with Manager():
    pass
Run Code Online (Sandbox Code Playgroud)

如果我们get_manager()在上面的三个块中放置了一个函数,是否有任何实现可以返回封闭的上下文管理器,或者至少它们的__exit__函数?

在第一种情况下,这显然很容易,但我想不出能让它在另外两种情况下工作的方法.我怀疑是否可以获得整个上下文管理器,因为值堆栈会在SETUP_WITH操作码之后立即弹出.但是,由于__exit__函数存储在块堆栈中SETUP_WITH,是否有某种方法可以访问它?

Stu*_*Gla 5

如果上下文管理器是一个类并且只有一个实例,那么您可以在堆上找到它:

import gc

class ConMan(object):
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print "enter %s" % self.name

    def found(self):
        print "You found %s!" % self.name

    def __exit__(self, *args):
        print "exit %s" % self.name


def find_single(typ):
    single = None
    for obj in gc.get_objects():
        if isinstance(obj, typ):
            if single is not None:
                raise ValueError("Found more than one")
            single = obj
    return single



def foo():
    conman = find_single(ConMan)
    conman.found()

with ConMan('the-context-manager'):
    foo()
Run Code Online (Sandbox Code Playgroud)

(免责声明:不要这样做)

  • 额外免责声明:不,真的,**不要**这样做。堆上的对象数量为 O(n),这可能是巨大的。翻译:这可能会非常慢。这甚至没有考虑到随机发现未收集的垃圾和其他您不想要的垃圾的可能性。 (2认同)

Nar*_*lei 5

不幸的是,正如评论中所讨论的那样,这在所有情况下都是不可能的.创建上下文管理器时,运行以下代码(至少在cPython 2.7中.我无法对其他实现发表评论):

    case SETUP_WITH:
    {
        static PyObject *exit, *enter;
        w = TOP();
        x = special_lookup(w, "__exit__", &exit);
        if (!x)
            break;
        SET_TOP(x);
        /* more code follows... */
    }
Run Code Online (Sandbox Code Playgroud)

__exit__方法被推入具有SET_TOP宏的堆栈,其定义为:

#define SET_TOP(v)        (stack_pointer[-1] = (v))
Run Code Online (Sandbox Code Playgroud)

反过来,堆栈指针在帧eval开始时设置为帧的值堆栈的顶部:

stack_pointer = f->f_stacktop;
Run Code Online (Sandbox Code Playgroud)

其中f是frameobject.h中定义的帧对象.对我们来说不幸的是,这就是路径停止的地方.python可访问框架对象仅使用以下方法定义:

static PyMemberDef frame_memberlist[] = {
    {"f_back",          T_OBJECT,       OFF(f_back),    RO},
    {"f_code",          T_OBJECT,       OFF(f_code),    RO},
    {"f_builtins",      T_OBJECT,       OFF(f_builtins),RO},
    {"f_globals",       T_OBJECT,       OFF(f_globals), RO},
    {"f_lasti",         T_INT,          OFF(f_lasti),   RO},
    {NULL}      /* Sentinel */
};
Run Code Online (Sandbox Code Playgroud)

不幸的是,不包括f_valuestack我们需要的东西.这是有道理的,因为f_valuestack是类型的PyObject **,需要包装在一个对象中,以便可以从python访问任何方式.

TL; DR:__exit__我们正在寻找的方法只位于一个地方,一个框架对象的值堆栈,而cPython不会使值堆栈可以被python代码访问.