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 consolein <module>()
/home/maric/<ipython consolein __getattr__(self, name)
/home/maric/<ipython consolein __getattr__(self, name)
/home/maric/<ipython consolein __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