Ian Bicking wrote:
> I got a puzzler for y'all. I want to allow the editing of functions
> in-place. I won't go into the reason (it's for HTConsole --
> http://blog.ianbicking.org/introducing-htconsole.html), except that I
> really want to edit it all in-process and in-memory. So I want the
> identity of the function to remain the same, even as I edit the body
> and hopefully the signature too.
>
> Well, the reason is that I want to edit any function object, without
> having to know who has a reference to the function. This way editing a
> function or a method or a property.fget can all be done in the same
> way.
>
> The func_code attributes of functions is writable, but I don't know how
> to create the proper code object. Just compiling a new body isn't good
> enough.
>
The experimental module below updates many of the builtin types in-place,
including functions. Functions in particular are handled by
update_FunctionType. I use this in my own development environment to edit live
code i.e., as a better reload.
Michael
"""Update live code with new source.
Example:
>>> source1 = "def func1(a): return 2*a"
>>> namespace = {}
>>> exec source1 in namespace
>>> func1 = namespace["func1"]
>>> func1(2)
4
>>> source2 = "def func1(a, b): return 2*a+b"
>>> exec_update(namespace, source2, verbosity=2)
Updating: ,
.func1 Updated
True
>>> func1(2,2)
6
>>>
"""
import types
import gc
class UpdateException(Exception): pass
def exec_update(namespace, source, name="", verbosity = 1):
module_proxy.verbosity = verbosity
if verbosity == 2:
print "Updating: %s, %s" % (type(namespace), name)
if isinstance(namespace, types.ModuleType):
proxy = module_proxy(namespace)
elif isinstance(namespace, (type, types.ClassType)):
proxy = cls_proxy(namespace)
elif isinstance(namespace, dict):
proxy = dict_proxy(namespace, name)
else:
raise UpdateException, "Unrecognized namespace type: %s" %
type(namespace)
exec source in proxy.dict, proxy
return True
class module_proxy(object):
DO_NOT_COPY = ()
verbosity = 1
def __init__(self, module, name = ""):
self.dict = module.__dict__
self.namespace = module
self.name = getattr(module,"__name__", name)
def __contains__(self, key):
return key in self.dict
def _setitem(self,key,value):
self.dict[key] = value
def _delitem(self, key):
del self.dict[key]
def __getitem__(self, key):
return self.dict[key]
def __setitem__(self, key, value):
try:
obj = self.dict[key]
except KeyError:
if self.verbosity >= 1:
print "** %s.%s=%s (Binding)" % (self.name, key, repr(value))
self._setitem(key, value)
return True
try:
if update(obj, value):
if self.verbosity >= 1:
print "%s.%s Updated" % (self.name, key)
return True
else:
if self.verbosity >= 2:
print "%s.%s No change" % (self.name, key)
return False
except UpdateException:
if self.verbosity >= 1:
print "** %s.%s=>%s (Rebinding)" % (self.name, key,
repr(value))
self._setitem(key, value)
return True
def __delitem__(self, key):
if self.verbosity >= 1:
print "** del %s.%s" % (self.name, key)
self._delitem(key)
def update_ns(self, other_dict, delete_missing=False, skip = ()):
dirty = False
if delete_missing:
for attr in self.dict.keys():
if not((attr in other_dict) or (attr in skip)):
dirty = True
del self[attr]
for to_attr, to_obj in other_dict.iteritems():
if to_attr in skip:
continue
dirty |= self.__setitem__(to_attr, to_obj)
return dirty
class dict_proxy(module_proxy):
def __init__(self, my_dict, name = ""):
self.dict = my_dict
self.namespace = None
self.name = name
class cls_proxy(module_proxy):
def _setitem(self,key,value):
setattr(self.namespace,key,value)
def _delitem(self, key):
delattr(self.namespace, key)
def update_cls(self, other):
DONOTCOPY = set(["__name__","__bases__","__base__","__dict__",
"__doc__","__weakref__","__module__"])
# This will often fail if they are not equal, so find out first!
obj = self.namespace
try:
obj.__bases__ = other.__bases__
except TypeError, err:
raise UpdateException, err
fromdict = obj.__dict__
todict = other.__dict__
obj_slots = set(getattr(obj, "__slots__", ()))