Le Monday 28 July 2008 16:48:09 Enrico, vous avez écrit : > Hi there, > I have the following situation (I tryed to minimize the code to concentrate > > on the issue): > >>> class A(object): > > def __getattr__(self, name): > print 'A.__getattr__' > if name == 'a': return 1 > raise AttributeError('%s not found in A' % name) > > >>> class B(object): > > def __getattr__(self, name): > print 'B.__getattr__' > if name == 'b': return 1 > raise AttributeError('%s not found in B' % name) > > Both classes have a __getattr__ method. > > Now I want to have a class that inherits from both so I write: > >>> class C(B,A): > > pass > > The problem arise when I try something like this: > >>> c=C() > >>> c.a > > A.__getattr__ > 1 > > >>> c.b > > A.__getattr__ > > Traceback (most recent call last): > File "<pyshell#47>", line 1, in <module> > c.b > File "<pyshell#42>", line 5, in __getattr__ > raise AttributeError('%s not found in A' % name) > AttributeError: b not found in A > > I was expecting, after a fail in A.__getattr__, a call to the __getattr__ > method of B but it seems that after A.__getattr__ fails the exception stops > the flow. So, if I did understand well, B.__getattr__ will be never called > "automatically". I don't know if this has a reason, if it is a design > choice or what else, any explanation is welcome. >
No getattr is a lookup fallback, classes which implement them in a non-collaborative way are unlikely to be used for multiple inheritance. Given how multiple inheritance work and __getattr__ semantic, I was surprised it is not that easy to figure out how it could work in a collaborative, and how far fromm this are common implementation of __getattr__. Normally they should be implemented like that : >>>[89]: class A(object) : def __getattr__(self, name) : if name == 'a' : return 'a' try : g = super(A, self).__getattr__ except : raise AttributeError('no more __getattr__') return g(name) ....: ....: >>>[95]: class B(object) : def __getattr__(self, name) : if name == 'b' : return 'b' try : g = super(B, self).__getattr__ except : raise AttributeError('no more __getattr__') return g(name) .....: .....: >>>[101]: class C(A, B) : def __getattr__(self, name) : if name == 'c' : return 'c' try : g = super(C, self).__getattr__ except : raise AttributeError('no more __getattr__') return g(name) .....: .....: >>>[107]: C().a ...[107]: 'a' >>>[108]: C().b ...[108]: 'b' >>>[109]: C().c ...[109]: 'c' >>>[110]: C().k --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) /home/maric/<ipython console> in <module>() /home/maric/<ipython console> in __getattr__(self, name) /home/maric/<ipython console> in __getattr__(self, name) /home/maric/<ipython console> in __getattr__(self, name) AttributeError: no more __getattr__ > Since A and B are not written by me I can only work on C. The solution that > comes to my mind is to define a __getattr__ also in C and write something > > like: > >>> class C(A,B): > > def __getattr__(self, name): > try: > return A.__getattr__(self, name) > except AttributeError: > return B.__getattr__(self, name) > > >>> c=C() > >>> c.a > > A.__getattr__ > 1 > > >>> c.b > > A.__getattr__ > B.__getattr__ > 1 > > A better solution is welcome. There is no way to repair those clases for mulitple inheritance except monkey patching them. The idea of this patch would be : def collaborative_getattr(class_, old_name) : old_one = getattr(class_, old_name) def __getattr__(self, name) : try : return old_one(self, name) except AttributeError : try : g = super(class_, self).__getattr__ except : raise AttributeError('no more __getattr__') return g(name) if not getattr(C, '_C_fixed__', False) : C._C_fixed__ = C.__getattr__ C.__getattr__ = collaborative_getattr(C, '_C_fixed__') That said, if your class C is a real facade for its ancestors A and B (A and B won't appear at all in the hierarchies of your subclasses), your solution is near the best one in terms of simplicity-efficiency. I said near the best one because your __getattr__ isn't collaborative yet ! :). -- _____________ Maric Michaud -- http://mail.python.org/mailman/listinfo/python-list