I was wondering if it is somehow possible to modify Python code live, while keeping all state of instantiated objects and methods, like I think is possible in Lisp or Erlang (*) ?
Say, I have an active Python sessions, where I instantiated the foo
class from a self-written module:
class foo():
@classmethod
def do_something(self):
print "this is good"
Python command line:
>>> f =foo()
>>> f.do_something()
Now, I would like to change the print statement into something else (e.g. print "this is better"
). If I edit my module file to do so, and reload it, I have to re-instantiate the f
object. Is there a way to be able to just call f.do_something()
again without having to call f=foo()
first?
So, I have to do this:
>>> reload my_module
>>> f =foo()
>>> f.do_something() # with changed print statement
But I want to do this:
>>> reload my_module
>>> f.do_something() # with changed print statement
(*) I am basing this statement on the cool Erlang movie and this fragment from Practical Common Lisp: 'When the bug manifested in the wild--100 million miles away from Earth--the team was able to diagnose and fix the running code, allowing the experiments to complete.'
Edit: I've been thinking a bit more about this and maybe what I want is inherently flawed for applying to OO (i.e., what about the state of the class and methods). I think Erlang allows this because, as far as I recall, it is more about separate communicating objects, so live updating the code of an object makes more sense. I am not sure though, so still open for answers.
Edit2: Maybe the best way to describe what I want is recapitulate what I said in a comment in a post below: "When called, the methods just have to point to the new method definitions/locations."
Yes you can, pretty simple too. You want to change only the instance f
, not the class foo
, right?
>>> class foo():
@classmethod
def do_something(self):
print "this is good"
>>> f = foo()
>>> f.do_something()
this is good
>>> def new_ds():
print "this is better"
>>> f.do_something = new_ds
>>> f.do_something()
this is better
>>> f2 = foo()
>>> f2.do_something() #unchanged
this is good
This is almost certainly less than desirable due to the change in scope, but changes like this took place for me immediately upon reload
testmod.py -- initially
class foo():
@classmethod
def do_something(self):
outside_func()
def outside_func():
print "this is good"
testmod.py -- after change
class foo():
@classmethod
def do_something(self):
outside_func()
def outside_func():
print "this is better"
Interpreter
>>> import testmod
>>> f = testmod.foo()
>>> f.do_something()
this is good
>>> reload(testmod)
<module 'testmod' from 'C:\Python26\testmod.py'>
>>> f.do_something()
this is better
You can create a class decorator or a metaclass that makes sure that the class of the old objects is changed on class reload. Here's a working (at least for me) example, though I wouldn't suggest you to use it as it is, but use it as an inspiration to create something that matches your intentions and needs. (It is also not tested on classes that don't define __init__
, so be wary.)
import sys
import weakref
class _ObSet(weakref.WeakValueDictionary):
def add(self, ob):
self[id(ob)] = ob
def remove(self, ob):
del self[id(ob)]
def __iter__(self):
return self.itervalues()
def reloadable(cls):
# Change the __init__ of the old class to store the instances
# in cls.__instances (you might stick this into a class as a
# static method to avoid name collisions)
if '__init__' in vars(cls):
old_init = vars(cls)['__init__']
def __init__(self, *a, **kw):
self.__class__.__instances.add(self)
old_init(self, *a, **kw)
cls.__init__ = __init__
elif '__new__' in vars(cls):
old_new = vars(cls)['__new__']
def __new__(cls, *a, **kw):
self = old_new(cls, *a, **kw)
cls.__instances.add(self)
return self
cls.__new__ = __new__
else:
def __init__(self, *a, **kw):
self.__class__.__instances.add(self)
super(cls, self).__init__(*a, **kw)
cls.__init__ = __init__
cls.__instances = _ObSet()
module = sys.modules.get(cls.__module__)
if module is None:
return cls
old_cls = getattr(module, cls.__name__, None)
if old_cls is None:
return cls
# Change the bases of all subclasses of the old class
for ob in old_cls.__instances:
if ob.__class__ is old_cls:
ob.__class__ = cls
# Change the class of all instances of the old class
for child_cls in old_cls.__subclasses__():
child_cls.__bases__ = tuple(cls if base is old_cls else base
for base in child_cls.__bases__)
return cls
Here's an example of how it is used:
from reloading import reloadable
@reloadable
class A(object):
def __init__(self, a, b):
self.a = a
self.b = b
class B1(A):
def __init__(self, c, *a):
super(B1, self).__init__(*a)
self.c = c
@reloadable
class B2(A):
def __init__(self, c, *a):
super(B2, self).__init__(*a)
self.c = c
And then how it works:
>>> import test_reload
>>> a = test_reload.A(1, 2)
>>> b1 = test_reload.B1(1, 2, 3)
>>> b2 = test_reload.B2(1, 4, 6)
>>> isinstance(a, test_reload.A)
True
>>> isinstance(b1, test_reload.A)
True
>>> isinstance(b1, test_reload.B1)
True
>>> isinstance(b2, test_reload.A)
True
>>> isinstance(b2, test_reload.B2)
True
>>> reload(test_reload)
<module 'test_reload' from 'test_reload.pyc'>
>>> isinstance(a, test_reload.A)
True
>>> isinstance(b1, test_reload.A)
True
>>> isinstance(b1, test_reload.B1) # will fail, not @reloadable
False
>>> isinstance(b2, test_reload.A)
True
>>> isinstance(b2, test_reload.B2)
True
>>> a.a, a.b
(1, 2)
>>> b1.a, b1.b, b1.c
(2, 3, 1)
>>> b2.a, b2.b, b2.c
(4, 6, 1)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With