views:

521

answers:

5

Let's say you have an object that was instantiated from a class inside a module. Now, you reload that module. The next thing you'd like to do is make that reload affect that class.

mymodule.py
---
class ClassChange():
    def run(self):
        print 'one'

myexperiment.py
---
import mymodule
from mymodule import ClassChange  # why is this necessary?
myObject = ClassChange()
myObject.run()
>>> one
### later, i changed this file, so that it says print 'two'

reload(mymodule)
# trick to change myObject needed here
myObject.run()
>>> two

Do you have to make a new ClassChange object, copy myObject into that, and delete the old myObject? Or is there a simpler way?

Edit: The run() method seems like a static class style method but that was only for the sake of brevity. I'd like the run() method to operate on data inside the object, so a static module function wouldn't do...

+4  A: 

You have to make a new object. There's no way to magically update the existing objects.

Read the reload builtin documentation - it is very clear. Here's the last paragraph:

If a module instantiates instances of a class, reloading the module that defines the class does not affect the method definitions of the instances — they continue to use the old class definition. The same is true for derived classes.

There are other caveats in the documentation, so you really should read it, and consider alternatives. Maybe you want to start a new question with why you want to use reload and ask for other ways of achieving the same thing.

nosklo
Thanks for the answer, albeit a gloomy one. I want the user to be able to rewrite code for an object, and have that code be put into affect. So for example, you have a planet object, and creatures on the planet. You want to change the planet code, so the weather on the planet changes behavior. All the creatures have pointers to that planet, and the planet data is huge, so deleting/creating it would be painful. Maybe reload isn't appropriate for this scenario...
Don't be __too__ gloomy, aidave -- see my answer... as long as you know which instances you want to update, and I show you one way to achieve that, assigning their __class__ or running them through a method to possibly add or remove some attributes is quite feasible (whether you want to use reload to get the new class object, or any other approach for the same purpose, is quite a secondary issue).
Alex Martelli
+1  A: 

The following code does what you want, but please don't use it (at least not until you're very sure you're doing the right thing), I'm posting it for explanation purposes only.

mymodule.py:

class ClassChange():
    @classmethod
    def run(cls,instance):
        print 'one',id(instance)

myexperiment.py:

import mymodule

myObject = mymodule.ClassChange()
mymodule.ClassChange.run(myObject)

# change mymodule.py here

reload(mymodule)
mymodule.ClassChange.run(myObject)

When in your code you instanciate myObject, you get an instance of ClassChange. This instance has an instance method called run. The object keeps this instance method (for the reason explained by nosklo) even when reloading, because reloading only reloads the class ClassChange.

In my code above, run is a class method. Class methods are always bound to and operate on the class, not the instance (which is why their first argument is usually called cls, not self). Wenn ClassChange is reloaded, so is this class method.

You can see that I also pass the instance as an argument to work with the correct (same) instance of ClassChange. You can see that because the same object id is printed in both cases.

balpha
Interesting. That may actually work for what I had in mind, but is a totally different approach to what I had envisioned!
Keep all the good advice in mind you got from nosklo and also from Alex Martelli in your previous question. This is a very difficult topic.
balpha
+1  A: 

I'm not sure if this is the best way to do it, or meshes with what you want to do... but this may work for you. If you want to change the behavior of a method, for all objects of a certain type... just use a function variable. For example:


def default_behavior(the_object):
  print "one"

def some_other_behavior(the_object):
  print "two"

class Foo(object):
  # Class variable: a function that has the behavior
  # (Takes an instance of a Foo as argument)
  behavior = default_behavior

  def __init__(self):
    print "Foo initialized"

  def method_that_changes_behavior(self):
    Foo.behavior(self)

if __name__ == "__main__":
  foo = Foo()
  foo.method_that_changes_behavior() # prints "one"
  Foo.behavior = some_other_behavior
  foo.method_that_changes_behavior() # prints "two"

  # OUTPUT
  # Foo initialized
  # one
  # two

You can now have a class that is responsible for reloading modules, and after reloading, setting Foo.behavior to something new. I tried out this code. It works fine :-).

Does this work for you?

Tom
Thanks for the response, but what happens when you need more than static behavior across the class? I should have been more specific. The function will need to utilize data specific to that instantiation.
What do you mean? You could change behavior() to take an object... like behavior(the_object), and pass self to it. ie - Foo.behavior(self) (in method_that_changes_behavior)
Tom
That way you can use state that is specific to the object.
Tom
That seems like it would work, but... I would have to keep adding new functions, as the user keeps changing the code. Let say the user can write the code in behavior, it would change frequently, I am not sure how to manage that, but with Python anything is possible...
Can you be a bit more clear on what you are confused about? behavior is not a method. It's a class variable to which you will find a function. The function will be expected to take a Foo. Now, anyone can write a function that takes a Foo as the arg. It doesn't matter how they do it... you can load such a function from a module on the fly. All you need to know is which function to bind to behavior. As soon as you load the module, you can say Foo.behavior = new_function_that_takes_a_foo. Does this make sense? It's just an extra line of code after you load the module.
Tom
"Can you be a bit more clear on what you are confused about?" haha, of course not, or i wouldnt be confused! ;) I am just wondering how to be a bit more elegant because the user would always be saying "ok all i need to do is change the code in function run()", but I wouldnt expect them to add a new function, etc. But basically I could add some kind of management structure to replace the run() pointer without the user knowing.
Yes, create a class that is responsible for reloading modules (if you need to), and after it reloads, it can reset the class variable behavior :-). I am going to edit my post, because as it looks now, it won't run. I am going to make it working code.
Tom
+8  A: 

To update all instances of a class, it is necessary to keep track somewhere about those instances -- typically via weak references (weak value dict is handiest and general) so the "keeping track" functionality won't stop unneeded instances from going away, of course!

You'd normally want to keep such a container in the class object, but, in this case, since you'll be reloading the module, getting the old class object is not trivial; it's simpler to work at module level.

So, let's say that an "upgradable module" needs to define, at its start, a weak value dict (and an auxiliary "next key to use" int) with, say, conventional names:

import weakref
class _List(list): pass   # a weakly-referenceable sequence
_objs = weakref.WeakValueDictionary()
_nextkey = 0
def _register(obj):
  _objs[_nextkey] = List((obj, type(obj).__name__))
  _nextkey += 1

Each class in the module must have, typically in __init__, a call _register(self) to register new instances.

Now the "reload function" can get the roster of all instances of all classes in this module by getting a copy of _objs before it reloads the module.

If all that's needed is to change the code, then life is reasonably easy:

def reload_all(amodule):
    objs = getattr(amodule, '_objs', None)
    reload(amodule)
    if not objs: return  # not an upgraable-module, or no objects
    newobjs = getattr(amodule, '_objs', None)
    for obj, classname in objs.values():
        newclass = getattr(amodule, classname)
        obj.__class__ = newclass
        if newobjs: newobjs._register(obj)

Alas, one typically does want to give the new class a chance to upgrade an object of the old class to itself more finely, e.g. by a suitable class method. That's not too hard either:

def reload_all(amodule):
    objs = getattr(amodule, '_objs', None)
    reload(amodule)
    if not objs: return  # not an upgraable-module, or no objects
    newobjs = getattr(amodule, '_objs', None)
    for obj, classname in objs:
        newclass = getattr(amodule, classname)
        upgrade = getattr(newclass, '_upgrade', None)
        if upgrade:
            upgrade(obj)
        else:
            obj.__class__ = newclass
        if newobjs: newobjs._register(obj)

For example, say the new version of class Zap has renamed an attribute from foo to bar. This could be the code of the new Zap:

class Zap(object):
    def __init__(self):
        _register(self)
        self.bar = 23

    @classmethod
    def _upgrade(cls, obj):
        obj.bar = obj.foo
        del obj.foo
        obj.__class__ = cls

This is NOT all -- there's a LOT more to say on the subject -- but, it IS the gist, and the answer is WAY long enough already (and I, exhausted enough;-).

Alex Martelli
Wow, very interesting read :-). I think my answer still has some merit if the goal is very simple - to change the behavior of a single function. However, your answer is obviously far more general and flexible with respect to the things you can change. For that, +1. And if I could, I would give you more +1's since I learn at least one interesting thing from your posts everyday :-).
Tom
@Tom, absolutely, I agree your answer could indicate an alternate architecture that's suitable for simpler goals -- and "as simple as possible, but no simpler" is sure close to the golden rule!-) Tx for the +1, and the kudos -- I've wrestled with such problems (and sometime the problems won the bout;-) for so long, it's only fair for me to "pay forwards" the help I got when _I_ was less experienced and do my best to help others out!-)
Alex Martelli
This is an excellent idea Alex. Thank you. I am keeping track of my objects already, that is not a problem. In fact, this is more flexible, because then the user can choose to reload all objects, just one, some, or none and have future objects use the new code. I will be giving your solution a shot this week and report back here. Thanks again!
+1  A: 

There are tricks to make what you want possible.

Someone already mentioned that you can have a class that keeps a list of its instances, and then changing the class of each instance to the new one upon reload.

However, that is not efficient. A better method is to change the old class so that it is the same as the new class.

Take a look at an old article I wrote which is probably the best you can do for a reload that will modify old objects.

http://www.codexon.com/posts/a-better-python-reload

Unknown