From: Rolando Espinoza La Fuente on 23 Jul 2010 22:42 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
From: Steven D'Aprano on 23 Jul 2010 23:15 On Fri, 23 Jul 2010 22:42:28 -0400, Rolando Espinoza La Fuente 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: [...] > 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
From: Benjamin Kaplan on 24 Jul 2010 00:28 On Fri, Jul 23, 2010 at 7:42 PM, Rolando Espinoza La Fuente <darkrho(a)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 >
From: Rolando Espinoza La Fuente on 24 Jul 2010 00:43 On Sat, Jul 24, 2010 at 12:28 AM, Benjamin Kaplan <benjamin.kaplan(a)case.edu> wrote: [...] > > 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'>) 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
From: Michele Simionato on 24 Jul 2010 02:32
On Jul 24, 4:42 am, Rolando Espinoza La Fuente <dark...(a)gmail.com> wrote: > Finally everything make sense. And make think about be careful when > doing multiple inheritance. > > Any thoughts? > > ~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). |