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
> 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:
[...]
> Finally everything make sense. And make think about be careful when
> doing multiple inheritance.
>
> Any thoughts?
Wow. Nice work, thanks for taking the time for documenting this publicly.
--
Steven
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
>
Yes, actually I looked at __mro__ to confirm that I was right.
>>>> '__str__' in BBar.__dict__
> False
>>>> '__str__' in Bar.__dict__
> True
I see! I couldn't figure out how to find if a method is defined within
given class.
>>>> for cls in BBar.__mro__ :
> if '__str__' in cls.__dict__ :
> print cls
> break
>
>
> <class '__main__.Bar'>
This is good one! It could save time figuring out where a method comes from.
Anyway, was a good exercise to figure out the mro by hand :)
Thanks for your comments Benjamin and Steven.
~Rolando
I am not fond of multiple inheritance either and I wrote at length
about the dangers of it. If you do not know it already, you may be
interested in reading my "Mixins considered harmful" series
http://www.artima.com/weblogs/viewpost.jsp?thread=246341 (together
with any blog posts on super and related subjects).
If you want to stay sane, don't inherit from ANY class unless
A. you own it, or
B. it's explicitly documented as supporting inheritance
Furthermore, if you want to stay sane, don't mulitply inherit from any
class unless
A. you own it, or
B. it's explicitly documented as supporting MULTIPLE inheritance
Inheritance is always risky if you don't know what you're inheriting
from.
Carl Banks