One of the annoyances of Python development can be that when you're
writing & interactively testing a class, old instances remain
instances of the old class. This is the irritation that this
metaclass seeks to remove.
import weakref, inspect
def our_init(self, *args, **kw):
if self.__oinit__:
self.__oinit__(*args, **kw)
self.__class__.instances.append(weakref.ref(self))
class InstanceTracker(type):
def __new__(self, name, bases, ns):
ns["__oinit__"] = ns.get("__init__")
ns["__init__"] = our_init
t = type.__new__(self, name, bases, ns)
t.instances = []
return t
def get_instances(self):
self.instances = [r for r in self.instances if r() is not None]
return [r() for r in self.instances]
class Foo:
__metaclass__ = InstanceTracker
def __init__(self, arg):
self.arg = arg
class AutoReloader(InstanceTracker):
def __new__(self, name, bases, ns):
t = InstanceTracker.__new__(self, name, bases, ns)
f = inspect.currentframe().f_back
for d in [f.f_locals, f.f_globals]:
if d.has_key(name):
o = d[name]
for i in o.get_instances():
i.__class__ = t
t.instances.append(weakref.ref(i))
break
return t
class Bar:
__metaclass__ = AutoReloader
def meth(self, arg):
print arg
b = Bar()
class Bar:
__metaclass__ = AutoReloader
def meth(self, arg):
print arg
# now b is "upgraded" to the new Bar class:
b.meth(1)
The idea is that you make you class an AutoReloader and then on
reload()s, old instances will be converted to instances of the fresh
class.
There are warts -- assigning to __class__ can fail, for one -- but I
think this should work in the common case. And when deployment time
arrives, you can just remove the "__metaclass__ = AutoReloader" lines.
Tomorrow I'll try to get around to writing it up as a cookbook entry
(so long as there's nothing similar already there -- I haven't
looked).
Comments? In particular, the way I override __init__ seems icky.
Cheers,
M.
PS: /good/ sigmonster :-)
--
ARTHUR: Don't ask me how it works or I'll start to whimper.
-- The Hitch-Hikers Guide to the Galaxy, Episode 11
> I'm on my way out, so I don't really have time to explain this, but
> here's a little something that may ease interactive development with
> Python.
Here's a better version of what I had before.
It doesn't interact well with inheritance (see comments at the end),
and there's not a lot I can do about that, because the __bases__
attribute of new style classes is not mutable. This might change in
Python 2.3...
hey-i-don't-care-if-noone's-listening,-i-think-it's-neat-ly y'rs
M.
import weakref, inspect
class MetaInstanceTracker(type):
def __new__(cls, name, bases, ns):
t = super(MetaInstanceTracker, cls).__new__(cls, name, bases, ns)
t.__instance_refs__ = []
return t
def __instances__(self):
instances = [(r, r()) for r in self.__instance_refs__]
instances = filter(lambda (x,y): y is not None, instances)
self.__instance_refs__ = [r for (r, o) in instances]
return [o for (r, o) in instances]
def __call__(self, *args, **kw):
instance = super(MetaInstanceTracker, self).__call__(*args, **kw)
self.__instance_refs__.append(weakref.ref(instance))
return instance
class InstanceTracker:
__metaclass__ = MetaInstanceTracker
class MetaAutoReloader(MetaInstanceTracker):
def __new__(cls, name, bases, ns):
new_class = super(MetaAutoReloader, cls).__new__(
cls, name, bases, ns)
f = inspect.currentframe().f_back
for d in [f.f_locals, f.f_globals]:
if d.has_key(name):
old_class = d[name]
for instance in old_class.__instances__():
instance.change_class(new_class)
new_class.__instance_refs__.append(
weakref.ref(instance))
break
return new_class
class AutoReloader:
__metaclass__ = MetaAutoReloader
def change_class(self, new_class):
self.__class__ = new_class
class Bar(AutoReloader):
pass
class Baz(Bar):
pass
b = Bar()
b2 = Baz()
class Bar(AutoReloader):
def meth(self, arg):
print arg
if __name__ == '__main__':
# now b is "upgraded" to the new Bar class:
b.meth(1)
# unfortunately, Baz instances can't join the fun:
try:
b2.meth()
except AttributeError:
print "nuts"
# even worse (and, actually, harder to deal with):
# new Baz() instances can't play either:
# unfortunately, Baz instances can't join the fun:
try:
Baz().meth()
except AttributeError:
print "nuts again"
--
I've even been known to get Marmite *near* my mouth -- but never
actually in it yet. Vegamite is right out.
UnicodeError: ASCII unpalatable error: vegamite found, ham expected
-- Tim Peters, comp.lang.python