multiple inheritance and __getattr__
Bruno Desthuilliers
bdesth.quelquechose at free.quelquepart.fr
Mon Jul 28 14:39:53 EDT 2008
Enrico a é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
That's what I would have expected.
> 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.
Indeed. You explicitely raise, so the lookup stops here. You'd need to
explicitely call on superclass instead to have B.__getattr__ called, ie:
class A(object):
def __getattr__(self, name):
if name == 'a':
return 1
return super(A, self).__getattr__(name)
class B(object):
def __getattr__(self, name):
if name == 'b':
return 2
return super(B, self).__getattr__(name)
class C(A, B):
pass
c = C()
c.a
=> 1
c.b
=> 2
> 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.
>
> Since A and B are not written by me I can only work on C.
Really ? You know, Python is a *very* dynamic language. If A and B are
ordinary Python classes (ie: not builtin types, not C extensions, etc),
you can monkeypatch them. But that's not necessarily the best thing to
do (it would require more work than your actual solution).
> 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)
or more generically:
class C(A, B):
def __getattr__(self, name):
for cls in type(self).__mro__[1:]:
try:
return cls.__getattr__(self, name)
except AttributeError:
pass
else:
raise AttributeError('blah blah')
You could also override __getattribute__, but then again, this wouldn't
be better (more code, and possible performances loss).
> A better solution is welcome.
If you don't have commit rights on the module(s) defining A and B, then
your solution is probably the simplest thing to do.
More information about the Python-list
mailing list