multiple inheritance and __getattr__

Bruno Desthuilliers bdesth.quelquechose at free.quelquepart.fr
Mon Jul 28 20:39:53 CEST 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