Is it possible to write to a python frame object as returned by sys._getframe() from python code running within the interpreter?

StackOverflow https://stackoverflow.com/questions/626835

Question

Apropos of This question, there is a bit of scaffolding within the interpreter to inspect frame objects, which can be retrieved by sys._getframe(). The frame objects appear to be read only, but I can't find anything obvious in the docs that explicitly states this. Can someone confirm whether these objects are writeable (in some way) or read only?

import sys

def foobar():
    xx='foo'
    ff = sys._getframe()
    ff.f_locals['xx'] = 'bar'
    print xx

if __name__ == '__main__':
    foobar()

This prints out 'foo' when run but the post below demonstrates the variable being writable when run from the current frame in an interactive shell.

Was it helpful?

Solution

From CPython source, Objects/frameobject.c:

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},
    {"f_exc_type",  T_OBJECT,       OFF(f_exc_type)},
    {"f_exc_value", T_OBJECT,       OFF(f_exc_value)},
    {"f_exc_traceback", T_OBJECT,   OFF(f_exc_traceback)},
    {NULL}    /* Sentinel */
};
...
static PyGetSetDef frame_getsetlist[] = {
    {"f_locals",    (getter)frame_getlocals, NULL, NULL},
    {"f_lineno",    (getter)frame_getlineno,
                    (setter)frame_setlineno, NULL},
    {"f_trace",     (getter)frame_gettrace, (setter)frame_settrace, NULL},
    {"f_restricted",(getter)frame_getrestricted,NULL, NULL},
    {0}
};

For the PyMemberDef, the flags RO or READONLY means it's attributes are read-only. For the PyGetSetDef, if it only has a getter, it's read only. This means all attributes but f_exc_type, f_exc_value, f_exc_traceback and f_trace are read-only after creation. This is also mentioned in the docs, under Data model.

The objects referred to by the attributes is not necessarily read-only. You could do this:

>>> f = sys._getframe()
>>> f.f_locals['foo'] = 3
>>> foo
3
>>>

Though this works in the interpreter, it fails inside functions. The execution engine uses a separate array for local variables (f_fastlocals), which is merged into f_locals on access, but the converse is not true.

>>> def foo():
...   x = 3
...   f = sys._getframe()
...   print f.f_locals['x']
...   x = 4
...   print f.f_locals['x']
...   d = f.f_locals
...   x = 5
...   print d['x']
...   f.f_locals
...   print d['x']
...
>>> foo()
3
4
4
5
>>>

On the global frame, f_local refers to f_globals, which makes this trick work in the interpreter. Modifying f_globals works, but affects the whole module.

OTHER TIPS

The f_locals['foo'] example by NXC works because the code is in module scope. In that case, f_locals is f_globals, and f_globals is both modifiable and modifications are reflected in the module.

Inside of function scope, locals() and f_locals are writable, but "[changes may not affect the values of local variables used by the interpreter]." 1 It's an implementation choice. In CPython there's a optimized bytecode for local variables, LOAD_FAST. In Python, local variables are (almost always) known once the function is defined, and CPython uses an index lookup to get the variable value, rather than a dictionary lookup.

In theory the dictionary lookup could proxy that table, but that's a lot of work for little gain.

The exceptions to "local variables are known" are if the function uses an exec statement, and the deprecated case of "from module import *". The generated byte code is different, and slower, for these cases.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top