Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it somehow possible to *live* modify Python code (like in Lisp or Erlang)

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

like image 232
Rabarberski Avatar asked Mar 25 '11 13:03

Rabarberski


2 Answers

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

EDIT

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
like image 111
jon_darkstar Avatar answered Nov 07 '22 17:11

jon_darkstar


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)
like image 25
Rosh Oxymoron Avatar answered Nov 07 '22 18:11

Rosh Oxymoron