[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)