MI can be difficult and confusing, and if the base classes don't cooperate -- well, cooperative MI won't work.
One way around this is to use composition instead. This class decorator will combine the source classes with the target class, ensuring that no duplications occur (raises a TypeError if there are any).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import new
class Compose(object):
def __init__(self, *parts):
self.parts = parts
def __call__(self, cls):
conflicts = dict()
parts = self.parts + (cls,)
for i, part1 in enumerate(parts):
for partn in parts[i+1:]:
for attr in dir(part1):
if attr[:2] == attr[-2:] == '__':
continue
if getattr(partn, attr, None):
if attr not in conflicts:
conflicts[attr] = [part1]
conflicts[attr].append(partn)
if conflicts:
text = []
for key, lst in conflicts.items():
text.append(' %s:' % key)
for c in lst:
text.append(' %s' % c)
text = '\n'.join(text)
raise TypeError("Conflicts while composing:\n%s" % text)
for part in self.parts:
for attr in dir(part):
if attr[:2] == attr[-2:] == '__':
continue
thing = getattr(part, attr)
thing = getattr(thing, '__func__', None) or thing
if callable(thing):
setattr(cls, attr, new.instancemethod(thing, None, cls))
else:
setattr(cls, attr, thing)
return cls
|
Brief example:
import unittest from compose import Compose
class Eggs(object): def test_eggs_01(self): print('testing eggs_01') def test_eggs_02(self): print('testing eggs_02')
class Spam(object): def test_spam_01(self): print('testing spam_01') def test_spam_02(self): print('testing spam_02')
@Compose(Spam, Eggs) class TestAll(unittest.TestCase): def setUp(self): print('Setting up...') def tearDown(self): print('Tearing down...') def test_something(self): print('testing something')
if __name__ == '__main__': unittest.main()
Let's try that again. :)