multiple inheritance and __getattr__
Maric Michaud
maric at aristote.info
Tue Jul 29 04:59:01 EDT 2008
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
More information about the Python-list
mailing list