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()