Is it possible to access enclosing context manager?

后端 未结 3 1631
灰色年华
灰色年华 2020-12-16 12:01

There are essentially three ways to use the with statement:

Use an existing context manager:

with manager:
    pass

Create a contex

相关标签:
3条回答
  • 2020-12-16 12:45

    If the context manager is a class and only ever has a single instance, then you could find it on the heap:

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

    (Disclaimer: Don't do this)

    0 讨论(0)
  • 2020-12-16 12:50

    The difference between this case and similar-appearing cases like super is that here there is no enclosing frame to look at. A with statement is not a new scope. sys._getframe(0) (or, if you're putting the code into a function, sys._getframe(1)) will work just fine, but it'll return you the exact same frame you have before and after the with statement.

    The only way you could do it would be by inspecting the bytecode. But even that won't help. For example, try this:

    from contextlib import contextmanager
    
    @contextmanager
    def silly():
        yield
    
    with silly():
        fr = sys._getframe(0)
    
    dis.dis(fr.f_code)
    

    Obviously, as SETUP_WITH explains, the method does get looked up and pushed onto the stack for WITH_CLEANUP to use later. So, even after POP_TOP removes the return value of silly(), its __exit__ is still on the stack.

    But there's no way to get at that from Python. Unless you want to start munging the bytecode, or digging apart the stack with ctypes or something, it might as well not exist.

    0 讨论(0)
  • 2020-12-16 12:55

    Unfortunately, as discussed in the comments, this is not possible in all cases. When a context manager is created, the following code is run (in cPython 2.7, at least. I can't comment on other implementations):

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

    The __exit__ method is pushed onto a stack with the SET_TOP macro, which is defined as:

    #define SET_TOP(v)        (stack_pointer[-1] = (v))
    

    The stack pointer, in turn, is set to the top of the frame's value stack at the start of frame eval:

    stack_pointer = f->f_stacktop;
    

    Where f is a frame object defined in frameobject.h. Unfortunately for us, this is where the trail stops. The python accessible frame object is defined with the 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 */
    };
    

    Which, unfortunaltey, does not include the f_valuestack that we would need. This makes sense, since f_valuestack is of the type PyObject **, which would need to be wrapped in an object to be accessible from python any way.

    TL;DR: The __exit__ method we're looking for is only located in one place, the value stack of a frame object, and cPython doesn't make the value stack accessible to python code.

    0 讨论(0)
提交回复
热议问题