multiple inheritance and __getattr__

Maric Michaud maric at aristote.info
Tue Jul 29 10:59:01 CEST 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