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.
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.
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.