understanding the mro (long)

Benjamin Kaplan benjamin.kaplan at case.edu
Sat Jul 24 00:28:55 EDT 2010


On Fri, Jul 23, 2010 at 7:42 PM, Rolando Espinoza La Fuente
<darkrho at gmail.com> wrote:
> TL;DR: if you want to stay sane, don't inherit two classes that share
> same inheritance graph
>
> I recently got puzzled by a bug from a legacy lib (ClientForm)
> which have this code:
>
>    class ParseError(sgmllib.SGMLParseError,
>                     HTMLParser.HTMLParseError,
>                     ):
>        pass
>
> And fails because takes __init__ from sgmllib and __str__ from HTMLParser
> where __str__ uses attributes set by HTMLParser's init.
>
> At first look, I thought was just matter to swap the inherit classes.
> But a deeper
> look take me to the python's mro reading:
> http://www.python.org/download/releases/2.3/mro/
>
> And to reproduce the error I code this:
>
> class Foo(object):
>    def __init__(self, msg):
>        self.msg = msg
>
>    def __str__(self):
>        return 'Foo: ' + self.msg
>
> class Bar(Exception):
>    def __init__(self, msg):
>        self.msg = msg
>
>    def __str__(self):
>        return 'Bar: ' + self.msg
>
> class A(Exception):
>    pass
>
> class B(RuntimeError):
>    pass
>
> class AFoo(A, Foo): pass
> class ABar(A, Bar): pass
>
> class BFoo(B, Foo): pass
> class BBar(B, Bar): pass
>
> print AFoo('ok') # ok
> print ABar('ok') # Bar: ok
>
> print BFoo('ok') # ok
> print BBar('fail') # AttributeError: ... not attribute 'msg'
>
> # EOF
>
> After running the code I was still confused. So I read carefully again
> the mro stuff. And ended doing this inheritance tree:
>
>       object (__init__, __str__)
>          |    \
>          |    Foo (__init__, __str__)
>          |
>  BaseException (__init__, __str__)
>          |
>          |
>          |
>      Exception (__init__)
>     /    |     \
>    A    |     Bar (__init__, __str__)
>          |
>  StandardError (__init__)
>          |
>          |
>          |
>    RuntimeError (__init__)
>    /
>   B
>
> Then I figure out the method resolution following the inheritance graph:
>  * AFoo(A, Foo):
>    __init__ from Exception
>    __str__  from BaseException
>
>  * ABar(A, Bar):
>    __init__ from Bar
>    __str__  from Bar
>
>  * BFoo(B, Foo):
>    __init__ from RuntimeError
>    __str__  from BaseException
>
>  * BBar(B, Bar):
>    __init__ from RuntimeError
>    __str__  from Bar
>
>
> Finally everything make sense. And make think about be careful when
> doing multiple inheritance.
>
> Any thoughts?
>

Two things:

First of all, avoid multiple inheritance if you can. It's usually
unnecessary in Python because of duck typing (unless you need to
inherit the actual behavior and not just a list of methods), and as
you've noticed, the MRO gets messy.


And second, not to in any way diminish the work you did tracing out
the inheritance tree and working through the inheritance, but Python
has easier ways of doing it :)

>>> BBar.__mro__
(<class '__main__.BBar'>, <class '__main__.B'>, <type
'exceptions.RuntimeError'>, <type 'exceptions.StandardError'>, <class
'__main__.Bar'>, <type 'exceptions.Exception'>, <type
'exceptions.BaseException'>, <type 'object'>)
>>> '__str__' in BBar.__dict__
False
>>> '__str__' in Bar.__dict__
True
>>> for cls in BBar.__mro__ :
	if '__str__' in cls.__dict__ :
		print cls
		break

	
<class '__main__.Bar'>

> ~Rolando
> --
> http://mail.python.org/mailman/listinfo/python-list
>



More information about the Python-list mailing list