0

I would like to call the same methods on all mixins of my class. Here are two variants:

class MixinA(object):
    def get_id(self):
        return "A"


class MixinB(object):
    def get_id(self):
        return "B"


class Base(object):
    def get_id(self):
        for base_class in inspect.getmro(self.__class__):
            return ",".join(base_class.get_id())


class Instance(MixinA, MixinB, Base):
    pass


class MyTestCase(unittest.TestCase):
    def test_multiple_mixin_methods(self):
        """
        Sadly, we cannot call all mixin methods.
        :return:
        """

        ids = set(Instance().get_id())
        print(ids)
        assert ids == {"A", "B"}

Sadly, this fails. I only get 'A' back. I would like a list containing 'A' and 'B', order does not matter.

Anything I am doing wrong here?

Many thanks

Dan Schien
  • 1,392
  • 1
  • 17
  • 29
  • 1
    Shouldn't Base be the first in inheritance for this to work ? Instance.get_id() just resolves to MixinA.get_id() right now. – polku Feb 16 '17 at 14:10
  • I believe you would need to do something like this to get the behavior you're after: http://stackoverflow.com/questions/20450911/python-call-parent-method-multiple-inheritance – Marcel Wilson Feb 16 '17 at 14:15

2 Answers2

1

What you need is to call to super. super calls the next method in the MRO so you will call A => B. you don't need the Base class.

class MixinA(object):
    def get_id(self):
        return "A", super(MixinA, self).get_id()


class MixinB(object):
    def get_id(self):
        return "B"


class Instance(MixinA, MixinB):
    pass

First the Instance().get_id() will call to A.get_id() then the call to super will call the next method in the MRO that is B

Update

class MixinA(object):
    def get_id(self):
        return "A"


class MixinB(object):
    def get_id(self):
        return "B"


class Base(object):
    def get_id(self):
       return set([base_class.get_id(self) for base_class in inspect.getmro(self.__class__)[2:-1]])


class Instance(Base, MixinA, MixinB):
    pass

Not like before the mixins are independent. in this example you need the Base class to be first in The MRO

Idan Haim Shalom
  • 1,234
  • 1
  • 11
  • 19
  • But then I need to think about the relationship between mixins. I would them to be able work independently and without knowledge of each other. – Dan Schien Feb 16 '17 at 15:25
  • Thank you for the update. The solution to place the Base class first seems to solve my problem. – Dan Schien Feb 16 '17 at 16:42
1

This snippet passes your tests, but it's brittle and IMHO a sure design smell. Also it requires that Base comes first in the mro (but that's usually what you want when using a base class and mixins):

class MixinA(object):
    def get_id(self):
        return "A"


class MixinB(object):
    def get_id(self):
        return "B"


class Base(object):
    def get_id(self):
        ids = set()
        mro = self.__class__.__mro__
        start = mro.index(Base) + 1
        for cls in mro__[start:-1]:
            get_id = getattr(cls, "get_id", None)
            if get_id:
                id = cls.get_id(self)
                ids.add(id)
        return ids

class Instance(Base, MixinA, MixinB):
    pass

You can get it to work (well, kind of...) with super calls too but it's not that pretty either and things start getting ugly when you add a third mixin (test the snippet with and without MixinC in Instance bases to see what I mean):

def get_super_id(id, cls, obj):
    try:
        supid = super(cls, obj).get_id()
        return id, supid 
    except AttributeError:
        return id

class MixinA(object):
    def get_id(self):
        return get_super_id("A", MixinA, self)


class MixinB(object):
    def get_id(self):
        return get_super_id("B", MixinB, self)

class MixinC(object):
    def get_id(self):
        return get_super_id("C", MixinC, self)


class Base(object):
    pass

class Instance(Base, MixinA, MixinB, MixinC):
    pass

Actually the thing I dislike most with the whole idea is that get_id returns either a single value or a collection. This breaks expectations in unpredictable ways.

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118