R
Rolando Espinoza La Fuente
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
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