Monday, 18 February 2008

Modifying Python frame locals

Occasionally I find myself wanting to modify the locals dictionary of a Python frame. Ideally it might have been possible by doing the following:

import sys
def f():
v = 1
sys._getframe().f_locals["v"] = 2
return v
But this does not work and v will be unchanged. As far as I can tell, within an acceptable range of effort, it is not possible to modify the local variables of a frame.

One situation where this is useful, is when you are doing code reloading. When you identify a new version of a class, you don't actually want to replace the old version. Instead you want to take the easier approach and update the old version to be the same as the new version.
def UpdateReferences(oldRef, newRef):
currentFrame = sys._getframe()
for referrer in gc.get_referrers(oldRef):
if type(referrer) is types.MethodType:
newRef2 = types.MethodType(newRef, referrer.im_self, referrer.im_class)
UpdateReferences(referrer, newRef2)
elif type(referrer) is types.FrameType:
if referrer is currentFrame:
# Problem case. Local variable replacement..

oldGlobals = existingFile.globals
for k, v in newClass.__dict__.iteritems():
if isinstance(v, types.UnboundMethodType):
f = v.im_func
# Duplicate the function but bound to the original globals dictionary.
g = types.FunctionType(f.func_code, oldGlobals, f.func_name,
f.func_defaults or ())
g.__doc__ = f.__doc__
if f.__dict__ is not None:
g.__dict__ = f.__dict__.copy()
# If the function already existed, update all references to it.
if hasattr(oldClass, k):
UpdateReferences(f, g)
# Inject the updated function on the original class.
setattr(oldClass, k, g)
There are other situations when writing support tools for a custom framework, where in order to allow programmers using it to write straightforward code, the inability to modify the local variables is a roadblock to the simplest solution - or to being able to do it at all.

If anyone knows of an acceptable way to do this, I would be interested in hearing about it.

1 comment:

  1. It's actually not a problem with f_locals but with Python scoping rules. Consider this version of your code, which works:

    import sys
    class f:
    v = 1
    sys._getframe().f_locals["v"] = 2
    print f.v prints 2 because the referencing rules have changed.

    One way I've seen people get around this is to actually *replace* frame.f_code with a new code object as similar as possible to the last one but with the changes you want to make. Of course, the effort to accomplish swapping f_code isn't much more fun than your wrapping effort.