Recipe 20.15. Upgrading Class Instances Automatically on reload
Credit: Michael Hudson, Peter Cogolo
Problem
You are developing a Python
module that defines a class, and you're trying
things out in the interactive interpreter. Each time you
reload the module, you have to ensure that
existing instances are updated to instances of the new, rather than
the old class.
Solution
First, we define a custom metaclass, which ensures its classes keep
track of all their existing instances:
import weakrefNow, we can subclass MetaInstanceTracker to obtain
class MetaInstanceTracker(type):
''' a metaclass which ensures its classes
keep track of their instances '''
def _ _init_ _(cls, name, bases, ns):
super(MetaInstanceTracker, cls)._ _init_ _(name, bases, ns)
# new class cls starts with no instances
cls._ _instance_refs_ _ = [ ]
def _ _instances_ _(cls):
''' return all instances of cls which are still alive '''
# get ref and obj for refs that are still alive
instances =
[(r, r( )) for r in cls._ _instance_refs_ _ if r( ) is not None]
# record the still-alive references back into the class
cls._ _instance_refs_ _ = [r for (r, o) in instances]
# return the instances which are still alive
return [o for (r, o) in instances]
def _ _call_ _(cls, *args, **kw):
''' generate an instance, and record it (with a weak reference) '''
instance = super(MetaInstanceTracker, cls)._ _call_ _(*args, **kw)
# record a ref to the instance before returning the instance
cls._ _instance_refs_ _.append(weakref.ref(instance))
return instance
class InstanceTracker:
''' any class may subclass this one, to keep track of its instances '''
_ _metaclass_ _ = MetaInstanceTracker
another custom metaclass, which, on top of the instance-tracking
functionality, implements the auto-upgrading functionality required
by this recipe's Problem:
import inspectHere is a usage example:
class MetaAutoReloader(MetaInstanceTracker):
''' a metaclass which, when one of its classes is re-built, updates all
instances and subclasses of the previous version to the new one '''
def _ _init_ _(cls, name, bases, ns):
# the new class may optionally define an _ _update_ _ method
updater = ns.pop('_ _update_ _', None)
super(MetaInstanceTracker, cls)._ _init_ _(name, bases, ns)
# inspect locals & globals in the stackframe of our caller
f = inspect.currentframe( ).f_back
for d in (f.f_locals, f.f_globals):
if name in d:
# found the name as a variable is it the old class
old_class = d[name]
if not isinstance(old_class, mcl):
# no, keep trying
continue
# found the old class: update its existing instances
for instance in old_class._ _instances_ _( ):
instance._ _class_ _ = cls
if updater: updater(instance)
cls._ _instance_refs_ _.append(weakref.ref(instance))
# also update the old class's subclasses
for subclass in old_class._ _subclasses_ _( ):
bases = list(subclass._ _bases_ _)
bases[bases.index(old_class)] = cls
subclass._ _bases_ _ = tuple(bases)
break
return cls
class AutoReloader:
''' any class may subclass this one, to get automatic updates '''
_ _metaclass_ _ = MetaAutoReloader
# an 'old class'
class Bar(AutoReloader):
def _ _init_ _(self, what=23):
self.old_attribute = what
# a subclass of the old class
class Baz(Bar):
pass
# instances of the old class & of its subclass
b = Bar( )
b2 = Baz( )
# we rebuild the class (normally via 'reload', but, here, in-line!):
class Bar(AutoReloader):
def _ _init_ _(self, what=42):
self.new_attribute = what+100
def _ _update_ _(self):
# compute new attribute from old ones, then delete old ones
self.new_attribute = self.old_attribute+100
del self.old_attribute
def meth(self, arg):
# add a new method which wasn't in the old class
print arg, self.new_attribute
if _ _name_ _ == '_ _main_ _':
# now b is "upgraded" to the new Bar class, so we can call 'meth':
b.meth(1)
# emits: 1 123
# subclass Baz is also upgraded, both for existing instances...:
b2.meth(2)
# emits: 2 123
# ...and for new ones:
Baz( ).meth(3)
# emits: 3 142
Discussion
You're probably familiar with the problem this
recipe is meant to address. The scenario is that
you're editing a Python module with your favorite
text editor. Let's say at some point, your module
mod.py looks like this:
class Foo(object):In another window, you have an interactive interpreter running to
def meth1(self, arg):
print arg
test your code:
>>> import modand it seems to be working. Now you edit mod.py
>>> f = mod.Foo( )
>>> f.meth1(1)
1
to add another method:
class Foo(object):Head back to the test session:
def meth1(self, arg):
print arg
def meth2(self, arg):
print -arg
>>> reload(mod)Argh! You forgot that f was an instance of the
module 'mod' from 'mod.pyc'
>>> f.meth2(2)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'Foo' object has no attribute 'meth2'
old mod.Foo!You can do two things about this situation. After reloading, either
regenerate the instance:
>>> f = mod.Foo( )or manually assign to f._ _class_ _:
>>> f.meth2(2)
-2
>>> f._ _class_ _ = mod.FooRegenerating works well in simple situations but can become very
>>> f.meth2(2)
-2
tedious. Assigning to the class can be automated, which is what this
recipe is all about.Class MetaInstanceTracker is a metaclass that tracks
instances of its instances. As metaclasses go, it
isn't too complicated. New classes of this metatype
get an extra _ _instance_refs_ _ class variable
(which is used to store weak references to instances) and an
_ _instances_ _ class method (which strips out dead
references from the _ _instance_refs_ _ list and
returns real references to the still live instances). Each time a
class whose metatype is MetaInstanceTracker gets
instantiated, a weak reference to the instance is appended to the
class' _ _instance_refs_ _ list.When the definition of a class of metatype
MetaAutoReloader executes, the namespace of the
definition is examined to determine whether a class of the same name
already exists. If it does, then it is assumed that this is a class
redefinition, instead of a class definition, and
all instances of the old class are updated to
the new class.
(MetaAutoReloader inherits from
MetaInstanceTracker, so such instances can easily be
found). All direct subclasses, found through the old
class' intrinsic _ _subclasses_ _
class method, then get their _ _bases_ _ tuples
rebuilt with the same change.The new class definition can optionally include a method _
_update_ _, whose job is to update the state (meaning the
set of attributes) of each instance, as the
instance's class transitions from the old version of
the class to the new one. The usage example in this
recipe's Solution presents a case in which one
attribute has changed name and is computed by different rules, as you
can tell by observing the way the _ _init_ _
methods of the old and new versions are coded; in this case, the job
of _ _update_ _ is to compute the new attribute
based on the value of the old one, then del the
old attribute for tidiness.This recipe's code should probably do more thorough
error checking; Net of error-checking issues, this recipe can also
supply some fundamental tools to start solving a problem that is
substantially harder than the one explained in this
recipe's Problem statement: automatically upgrade
classes in a long-running application, without needing to stop and
restart that application.Doing automatic upgrading in production code is more difficult than
doing it during development because many more issues must be
monitored. For example, you may need a form of locking to ensure the
application is in a quiescent state while a number of classes get
upgraded, since you probably don't want to have the
application answering requests in the middle of the upgrading
procedure, with some classes or instances already upgraded and others
still in their old versions. You also often encounter issues of
persistent storage because the application probably needs to update
whatever persistent storage it keeps from old to new versions when it
upgrades classes. And those are just two examples. Nevertheless, the
key component of such on-the-fly upgrading, which has to do with
updating instances and subclasses of old classes to new ones, can be
tackled with the tools shown in this recipe.
See Also
Docs for the built-in function reload in the
Library Reference and Python in a
Nutshell.