understanding the mro (long)

Rolando Espinoza La Fuente darkrho at gmail.com
Fri Jul 23 22:42:28 EDT 2010


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?

~Rolando



More information about the Python-list mailing list