[Python-Dev] Writing a mutable object problem with __setattr__

Aleks Totic a@totic.org
Tue, 25 Feb 2003 00:56:40 -0800


Hi,
I am trying to create a mutable object for a persistent object 
database, and I've ran into a problem. The semantics are:

Object gets created as a 'Swizzle'. Swizzle is a stub that 
intercepts any attempt to access object properties. When code 
tries to access any of swizzle's properties, the swizzle should 
automatically mutate into the stored object, by changing its 
class and properties.

With the __set/get/del/attr__ traps this seemed feasible in 
Python. But it turns out to be trickier than I've thought, my 
naive implementation is not working.

What happens is:
swizzle.__getattr__() gets called
   calls swizzle.load()
     swizzle.__class__ = stored_class
     swizzle.__dict__ = stored_data
   calls getattr(self, attr_name)
   calls swizzle.__getattr__() gets called
     and I enter an infinite loop and blow the stack

Since swizzle's __class__ and __dict__ have changed, shouldn't 
getattr use the new class to get attributes?

Why this is puzzling is that if I call swizzle.load() directly 
(bypassing __getattr__ trap), and then try referencing swizzle's 
fields, everything works. The object mutates successfully, and 
__getattr__ never gets called again.

Any hints on what might be going wrong? Hopefully others have 
struggled with the same problem before me.

Details of Python's class implementation and method dispatch 
would also be interesting. I've read Guido's Unifying Types 
essay, but I think I lack Python history required for its full 
understanding.

I've tried many different approaches:
- Swizzle inheriting from object (so that I can call super 
methods directly). This one would not let me assign into the 
__class__
- Swizzle calling different combinations of 
self.__setattr/setattr/setattribute

Here is my current code.

Thanks in advance,

Aleks

class Swizzle(object):
     """ A swizzle is a placeholder for an object that has not
     been loaded. It mutates into the loaded object upon access
     TODO: locking
     """
     def __init__(self, oid):
         self.__setattr__('oid', oid)

     def __getattr__(self, name):
         print "swizzle getattr called"
         # oid is the only attribute that does not cause a load
         if (name == 'oid'):
             return self.__dict__[name]
         self.load()
         return getattr(self,name)

     def __setattr__(self, name, value):
         """ setattr passes class & dict because these are called
         when we are mutating the object """
         print "swizzle setattr called"
         if (name == '__class__' or name == '__dict__' or name == 
'oid'):
             self.__dict__[name] = value
             return
         self.load()
         return setattr(self,name, value)

     def __delattr__(self, name):
         print "swizzle delattr called"
         self.load()
         return delattr(self, name)

     def load(self):
         """ TODO LOCK"""
         print "Trying to load ", self.oid
         RepositoryManager.manager.load(self)

def RepositoryManager.load(anItem)
  anItem.__dict__ = getProperties(anItem.oid)
  anItem.__class__ = getClass(anItem.oid)