a class auto upgrader

0 views
Skip to first unread message

Michael Hudson

unread,
Oct 29, 2002, 1:07:29 PM10/29/02
to
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.

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

Michael Hudson

unread,
Oct 31, 2002, 9:42:45 AM10/31/02
to
Michael Hudson <m...@python.net> writes:

> 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

Reply all
Reply to author
Forward
0 new messages