Michael
Michael

Reputation: 7809

How to improve a mixin structure in Python?

I have a simple mixin structure in Python. The code should be pretty self-explaining:

class Base:
    def __init__(self):
        pass

class MixinA:
    def __init__(self):
        self.x = 0
        self.y = 1
    def a(self):
        print('A: x = ' + str(self.x) + ', y = ' + str(self.y))

class MixinB:
    def __init__(self):
        self.x = 2
        self.z = 3
    def b(self):
        print('B: x = ' + str(self.x) + ', z = ' + str(self.z))

class MyFirstMix(MixinA, MixinB, Base):
    def __init__(self):
        Base.__init__(self)
        MixinB.__init__(self)
        MixinA.__init__(self)

class MySecondMix(MixinA, Base):
    def __init__(self):
        Base.__init__(self)
        MixinA.__init__(self)

I'd like to improve this a bit, so this leads to 3 questions/problems:

  1. MixinA and MixinB both have a member x. Is there a way to make sure, each of the class sees only its own x? As far as I know: No, there isn't.
  2. It's slightly cumbersome to call the constructor for each mixin in the mixed class. Is there a way to automatically call all constructors or to do something with the same effect?
  3. Is there a way to dynamically mixin something in-line, without explicitly creating a class? I'm looking for a syntax like: mix = Base() with MixinA

If my proposed structure is completely wrong, I'm also open for other recommendations on how to handle mixins.

Upvotes: 2

Views: 1393

Answers (2)

rojeeer
rojeeer

Reputation: 2011

For python class inherent, I believe there are some tricks you need to know:

  1. Class in python2 and python3 are quite different. Python2 support old-style class, but python3 support new-style class only. Simply speaking: in python3, classes always inherent from a base class object, even though you do not explicitly use it. Check Difference-between-Old-New-Class.

  2. Method Resolution Order (MRO). This determines how derived class search inherent members and functions. See MRO

  3. super function. Combined with MRO, you can easily call parent member or function, without explicitly know the name of parent class. See Super

Now comes to you questions:

  1. MixinA and MixinB both have a member x. Is there a way to make sure, each of the class sees only its own x?

I don't quit understand your meaning. When you refer a class member, you must refer it through its instance or class. So instance_of_MixinA.x and instance_of_MixinB.x are separated. If you are talking about class MyFirstMix(MixinA, MixinB, Base), it depends on how __init__ function is called. In your code, you first populate x by MixinB and then reset its value by MixinA.

  1. It's slightly cumbersome to call the constructor for each mixin in the mixed class. Is there a way to automatically call all constructors or to do something with the same effect.

Your designation make it impossible. You have to call all constructors.

  1. Is there a way to dynamically mixin something in-line, without explicitly creating a class?

I am not sure. But I can give you some suggestions: try outside __init__ members when def class (python3, if you used python2 take care of super):

class Base:
    def __init__(self):
        pass


class MixinA:
    x = 0
    y = 1


class MixinB:
    x = 2
    z = 3

    def b(self):
        print('B: x = ' + str(self.x) + ', z = ' + str(self.z))


class MyFirstMix(MixinA, MixinB, Base):
    def __init__(self):
        super().__init__()


class MySecondMix(MixinA, Base):
    def __init__(self):
        super().__init__()

The variables outside __init__ behaves quit different from inside variables: outside variables belongs to class and will be populated for all instances of this class, while inside variables belongs only to instance (referred by self when you define class), and will be populated only if __init__ is called. That's why you cannot use super to call all the constructors---super only call the priority parent's __init__. See variables-outsite-inside-init

This is a good solution to Mixin class. In above code, MyFirstMix inherents both MixinA and MixinB whose members are all class members (outside __init__). So instances of MyFirstMix will inherent all class members of MixinA and MixinB without call __init__. Here MixinA and MixinB own same class member x, but the MRO determines that when instances of MyFirstMix refer x, x from MixinA should be returned.

Hope this will be helpful. Thanks!

Upvotes: 2

holdenweb
holdenweb

Reputation: 37033

When your inheritance schemes start to suffer from these sorts of issues it's time to consider using a technique called composition instead. A good readable introduction to the topic here. The Wikipedia example is a bit less accessible, but also useful if you can handle the other programming languages. This StackExchange question also offers useful discussion.

At its simplest, rather than a class inheriting from SomeParent and mixing in the Mixin class, you instead have the SomeParent instances each create an instance of Mixin and use that to access the mixin class's functionality.

Upvotes: 1

Related Questions