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
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:
continue
# 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)
If anyone knows of an acceptable way to do this, I would be interested in hearing about it.
It's actually not a problem with f_locals but with Python scoping rules. Consider this version of your code, which works:
ReplyDeleteimport sys
class f:
v = 1
sys._getframe().f_locals["v"] = 2
print f.v
...it 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.