"Super" reload and a problem.

Jeremy Fincher tweedgeezer at hotmail.com
Fri Aug 30 14:08:28 CEST 2002


I need a more powerful reload() for the program I'm writing, a reload
that updates all instances of a class to the newly reloaded class
automatically.  Until earlier today I'd been using a modified form of
Thomas Heller's "autoreload.py," but it was (very obviously) written
prior to 2.2, and thus didn't mix well with the dict-proxies of
new-style class objects.

So I wrote one of my own, based on ideas I got from 'exarkun' in
#python on OPN, and apparently similar to Twisted's
twisted.python.rebuild function (at least in theory; in practice, I
believe they differ significantly).

def superReload(oldmodule):
    ###
    # So here's how this baby works:
    #   Reload the module.
    #   Iterate through the old module, finding classes and functions
that are
    #     also in the new module.
    #   Add an __getattribute__ or __getattr__ method to those classes
that are
    #     present in the new module.  This method will, when called,
change
    #     the instance's __class__ to point to the new class.
    #   Change the func_code, func_defaults, and func_doc of any
functions or
    #     methods or generators we run across, just in case someone's
holding
    #     a reference to them instead of calling them by name.
    ###
    """Reload a module and make objects auto-update."""
    # reload(module) modifies module in-place, so we need a copy of
its
    # __dict__ to iterate through.
    olddict = copy.copy(oldmodule.__dict__)
    newmodule = reload(oldmodule)
    newdict = newmodule.__dict__
    for (name, oldvalue) in olddict.iteritems():
        if name in newdict:
            newvalue = newdict[name]
            oldtype = type(oldvalue)
            # We have to pass in newvalue because of Python's scoping.
            def updater(self, s, newvalue=newvalue):
                # This function is to be an __getattr__ or
__getattribute__.
                try:
                    self.__class__ = newvalue
                except:
                    debug.recoverableException()
                    try:
                        del self.__class__.__getattribute__
                    except AttributeError:
                        del self.__class__.__getattr__
                return getattr(self, s)
            if oldtype == types.TypeType and \
               oldvalue.__module__ == newmodule.__name__:
                # New-style classes support __getattribute__, which is
called
                # on *any* attribute access, so they get updated the
first
                # time they're used after a reload.
                oldvalue.__getattribute__ = updater
            elif oldtype == types.ClassType and \
                 oldvalue.__module__ == newmodule.__name__:
                # Old-style classes can only use getattr, so they
might not
                # update right away.  Hopefully they will, but to
solve this
                # problem I just use new-style classes.
                oldvalue.__getattr__ = updater
            elif oldtype == type(newvalue):
                if oldtype == types.FunctionType or\
                   oldtype == types.GeneratorType:
                    oldvalue.func_code = newvalue.func_code
                    oldvalue.func_defaults = newvalue.func_defaults
                    oldvalue.func_doc = newvalue.func_doc
                elif oldtype == types.MethodType:
                    oldfunc = oldvalue.im_func
                    newfunc = newvalue.im_func
                    oldfunc.func_code = newfunc.func_code
                    oldfunc.func_defaults = newfunc.func_defaults
                    oldfunc.func_doc = newfunc.func_doc
    # Update the linecache, so tracebacks show the proper lines.
    linecache.checkcache()
    return newmodule

I ran into several problems while implementing this.  reload()
apparently modifies the module's __dict__ in place, but then returns
the "new" module.  Also, I apparently misunderstood Python's nested
scopes, and thus had difficulties with "updater" until I put the
default "newvalue" argument in there.  I also learned that an
instance's __class__ attribute can't be changed if the class defines
__slots__, but that wasn't such a huge deal.

The problem I'm having now, however, is when this class is reloaded:

class nick(str):
    """This class does case-insensitive comparisons of nicks."""
    def __init__(self, s):
        self.lowered = nickToLower(s)
    def __eq__(self, s):
        try:
            return nickToLower(s) == self.lowered
        except:
            return False
    def __hash__(self):
        return hash(self.lowered)


But it doesn't define __slots__!  I can't figure out why I get this
error when the module containing that class is reloaded, despite the
fact that the code hadn't been changed:

Traceback (most recent call last):
  File "src/world.py", line 109, in updater
    self.__class__ = newvalue
TypeError: __class__ assignment: 'nick' object layout differs from
'nick'


Not only do I get that error, but it seems to loop infinitely with
that error.  I put checks in updater, to delete the class attribute I
added (either __getattribute__ or __getattr__), but it doesn't appear
to be being called.

Any ideas, or comments on how to make superReload more robust?

Thanks,
Jeremy



More information about the Python-list mailing list