[Python-Dev] Writing a mutable object problem with __setattr__
Guido van Rossum
guido@python.org
Tue, 25 Feb 2003 09:50:12 -0500
> 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.
This is a common pattern; Zope (really ZODB) uses this and calls the
incomplete objects "ghosts". Zope doesn't change the __class__
though.
> 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?
That's what I woul'd have thought too. Maybe the problem is that
you're really requesting a non-existent attribute? Do you know which
attribute name is being requested? That's the first information you
need.
> 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__
In Python 2.2.2 you can set __class__, as long as __class__ has a
compatible instance lay-out (at the C implementation level).
With new-style objects, you can use __getattribute__ instead of
__getattr__ for more control (and also more opportunities to blow the
stack :-).
> - Swizzle calling different combinations of
> self.__setattr/setattr/setattribute
I don't understand -- these names don't exist.
> 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
Ah, here's the catch. You can't implement assignment to __dict__ or
__class__ by doing "self.__dict__[name] = value".
Use new-style classes and you'll be much happier: you can invoke the
superclass __setattr__ to do the magic.
> 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)
--Guido van Rossum (home page: http://www.python.org/~guido/)