multiple inheritance in python starts to fall apart when you want to mash together two very similar objects. I haven't found a clean way to get this example to work (without hardcoding the super calls to self.rollback_db() or doubling up implementations of rollback_db, both of which seem like they shouldn't be necessary).
Does anyone have a suggestion? Aside from this gotcha, I often find multiple inheritance to be an elegant solution.
"""
>>> rolledback = set()
>>> class A(object):
... def rollback(self):
... self.rollback_db()
...
... def rollback_db(self):
... rolledback.add('A')
...
>>> class B(object):
... def rollback(self):
... self.rollback_db()
...
... def rollback_db(self):
... rolledback.add('B')
...
>>> class C(A, B):
... def rollback(self):
... A.rollback(self)
... B.rollback(self)
...
>>> c = C()
>>> c.rollback()
>>> rolledback
set(['A', 'B'])
"""
import doctest
doctest.testmod()
output is...
kumar$ python test_mro.py
**********************************************************************
File "test_mro.py", line 24, in __main__
Failed example:
rolledback
Expected:
set(['A', 'B'])
Got:
set(['A'])
**********************************************************************
and, yes, this was not a fun one to debug :( Thankfully this was discovered in a functional test of --dry-run!
UPDATE
There are some helpful suggestions in the comments, but I still don't see a solution! Here is an example fixed a little bit using super(), but still only 50% there:
"""
>>> rolledback = []
>>> class Rollbackable(object):
... def rollback(self):
... pass
...
>>> class A(Rollbackable):
... def rollback(self):
... super(A, self).rollback()
... rolledback.append('A')
... self.rollback_db()
...
... def rollback_db(self):
... rolledback.append('db(A)')
...
>>> class B(Rollbackable):
... def rollback(self):
... super(B, self).rollback()
... rolledback.append('B')
... self.rollback_db()
...
... def rollback_db(self):
... rolledback.append('db(B)')
...
>>> class C(A, B):
... def rollback(self):
... super(C, self).rollback()
...
>>> c = C()
>>> c.rollback()
>>> rolledback
['B', 'db(B)', 'A', 'db(A)']
"""
import doctest
doctest.testmod()
... and the output...
kumar$ python test_mro.py
**********************************************************************
File "test_mro.py", line 31, in __main__
Failed example:
rolledback
Expected:
['B', 'db(B)', 'A', 'db(A)']
Got:
['B', 'db(A)', 'A', 'db(A)']
**********************************************************************
1 items had failures:
1 of 8 in __main__
***Test Failed*** 1 failures.
UPDATE #2
Thanks for all the helpful comments. It appears that the only way to accomplish this in python is to actually change the self.rollback_db() to a private method, via python magic underscores, like so: self.__rollback_db(). The downside to this of couse is that no subclass can ever override __rollback_db(). This can be inflexible at times. For example, I've wanted to override private methods before but couldn't (coincidentally, when trying to extend doctest; Instead I had to copy/paste about 20 lines of code). So use private methods only if you absolutely have to. In this case it makes that rollback_db() is private since it only rolls back a single db transaction and that's it. Also take note that super doesn't need to be called from class C since everything just works itself out (because of super in the base classes).
Now ... here is what passes the doctest!
"""
>>> rolledback = []
>>> class Rollbackable(object):
... def rollback(self):
... pass
...
>>> class A(Rollbackable):
... def rollback(self):
... super(A, self).rollback()
... rolledback.append('A')
... self.__rollback_db()
...
... def __rollback_db(self):
... rolledback.append('db(A)')
...
>>> class B(Rollbackable):
... def rollback(self):
... super(B, self).rollback()
... rolledback.append('B')
... self.__rollback_db()
...
... def __rollback_db(self):
... rolledback.append('db(B)')
...
>>> class C(A, B):
... pass
...
>>> c = C()
>>> c.rollback()
>>> rolledback
['B', 'db(B)', 'A', 'db(A)']
"""
import doctest
doctest.testmod()

Re: multiple inheritance woes
posted by Doug Napoleone on Monday Mar 26th, 2007 at 5:25p.m.
super is your friend!
>>> class C(A, B):
... def rollback(self):
... super(C, self).rollback()
...
That will fix your problems!
Re: multiple inheritance woes
posted by Kumar McMillan on Monday Mar 26th, 2007 at 5:46p.m.
huh. I changed the above mash-up of the two rollback() methods to be one, super(C, self).rollback() ... but it still gave me the same behavior as before. Can you post an example that passes the test above? I always thought super() only gives you the first method discovered by the MRO
Re: multiple inheritance woes
posted by Peter Fein on Monday Mar 26th, 2007 at 6:30p.m.
*All* of your methods need to call super(), including A and B.
This gets a little tricky when you have true multiple inheritance (like with mixins say), where there's no clear ancestor (ie, base class). In your case, the __mro__ is (C, A, B). If B tries to do super(B, self).rollback(), you'll get an exception b/c object doesn't have a rollback method.
The solution is to define an "abstract base class" (to abuse a C++ term) that has a rollback() method that does nothing (literally def rollback(): pass) and have A & B inherit from it.
Re: multiple inheritance woes
posted by Tiago Cogumbreiro on Monday Mar 26th, 2007 at 8:15p.m.
If possible moving the 'rollback_db' inside the 'rollback' method solves it:
def rollback(self):
A.rollback_db(self)
B.rollback_db(self)
Making the 'rollback_db' obfuscated^Wprivate is another solution:
class A(object):
def rollback(self):
self.__rollback_db()
And maybe you could use delegation:
class C(object):
def __init__(self, a, b):
self.a = a
self.b = b
def rollback(self):
self.a.rollback()
self.b.rollback()
Re: multiple inheritance woes
posted by Kumar McMillan on Monday Mar 26th, 2007 at 9:22p.m.
Thanks for all the suggestions, but stopping short of calling rollback_db() directly, I still don't see a solution. Is this just not possible? The super() does something I didn't think it did, it allows me to call both A.rollback and B.rollback (cool!) ... however, the reference to self in the subclasses are still wrong. I posted an updated example in case anyone has a fix.
Re: multiple inheritance woes
posted by Peter Otten on Tuesday Mar 27th, 2007 at 7:24a.m.
Make rollback_db() private in your updated version, i. e. rename it to __rollback_db().
Re: multiple inheritance woes
posted by Kumar McMillan on Thursday Mar 29th, 2007 at 10:21a.m.
Thanks for all the comments. Looks like a private method it is. I updated the post with the working code.
Re: multiple inheritance woes
posted by __doc__ on Sunday Apr 1st, 2007 at 4:51a.m.
I don't know if it's a smart idea, anyway. What you want to archieve is a particular class tied execution flow. That multiple-inheritance doesn't work the way you'd like it to can't be changed. But that doesn't mean you can't archieve the desired flow of control in a very similar fashion in python. So here goes, an alternative way to get there not using python's multiple inheritance at all.
http://paste.pocoo.org/show/1301/
Re: multiple inheritance woes
posted by Kumar McMillan on Monday Apr 2nd, 2007 at 10:12a.m.
interesting. This makes it so you don't have to create private methods, but there is a problem: if you want to call a super method you have to explicitly call it. The nice thing about the above solution is that super methods are called naturally (due to the MRO). I think that might make it a better solution, IMHO.
Although, what you may be getting at, and what someone else mentioned, is that delgation (over inheritance) may be a better idea. In my particular application delegation is used more than inheritance. There is [more or less] a command that has a stack of actions; each action is called sequentially.
But in the case of an action that uses two database connections (the above scenario), I think it makes more sense to use inheritance over delegation. The fact that two connections are needed is a detail of the action, not the command.
Re: multiple inheritance woes
posted by SmileyChris on Friday Aug 3rd, 2007 at 4:24a.m.
Another pretty obvious solution without private methods:
http://paste.pocoo.org/show/2178/
Re: multiple inheritance woes
posted by Roger on Thursday Jun 26th, 2008 at 6:11p.m.
I've been having a similar problem and I ended up doing something like (per your example):
replacing (in class A):
self.rollback_db()
with:
A.rollback_db(self)
Don't know if this was your exact problem, but it seems to work for me. It's annoying but I guess if a class calls one of its member functions you should avoid self.func() and use class.func(self).