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

2024-04-16 17:36:12 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

with manager:
    pass

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

^{pr2}$

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

with Manager():
    pass

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

第一种情况显然很简单,但我想不出在另外两种情况下能起作用的方法。我怀疑是否有可能获得整个上下文管理器,因为值堆栈是在SETUP_WITH操作码之后立即弹出的。但是,既然__exit__函数是由^{}存储在块堆栈上的,那么有什么方法可以访问它吗?在


Tags: 方法函数管理器get堆栈withsetupexit
3条回答

不幸的是,正如评论中所讨论的,这并非在所有情况下都可能。创建上下文管理器时,following code is run(至少在cpython2.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... */
    }

使用__exit__宏将SET_TOP方法推送到堆栈上,该宏是defined as

^{pr2}$

堆栈指针依次设置在frame eval开头的帧值堆栈的顶部:

stack_pointer = f->f_stacktop;

其中f是在frameobject.h中定义的帧对象。对我们来说不幸的是,这是线索停止的地方。python可访问框架对象是用following methods only定义的:

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 */
};

不幸的是,它没有包括我们需要的f_valuestack。这是有意义的,因为f_valuestackPyObject **类型,它需要包装在一个对象中,以便从python以任何方式访问。在

TL;DR:我们要寻找的__exit__方法只位于一个地方,即frame对象的值堆栈,而cPython不允许python代码访问值堆栈。在

这种情况与类似的出现案例(如super)之间的区别在于,这里没有可查看的封闭框架。with语句不是新范围。sys._getframe(0)(或者,如果您将代码放入函数中,sys._getframe(1))可以正常工作,但它将返回与with语句前后相同的帧。在

唯一的方法就是检查字节码。但即使这样也无济于事。例如,请尝试以下操作:

from contextlib import contextmanager

@contextmanager
def silly():
    yield

with silly():
    fr = sys._getframe(0)

dis.dis(fr.f_code)

显然,正如^{}所解释的那样,该方法确实会被查找并推送到堆栈上,供^{}以后使用。因此,即使POP_TOP删除了silly()的返回值,它的__exit__仍然在堆栈中。在

从Python那里走不到那里。除非您想开始咀嚼字节码,或者用ctypes或其他东西来挖掘堆栈,否则它可能不存在。在

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

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()

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

相关问题 更多 >