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,是否有某种方法可以访问它?
如果上下文管理器是一个类并且只有一个实例,那么您可以在堆上找到它:
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)
(免责声明:不要这样做)
不幸的是,正如评论中所讨论的那样,这在所有情况下都是不可能的.创建上下文管理器时,运行以下代码(至少在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代码访问.