76

I'd like to use a Mixin to always add some init functionality to my child classes which each inherit from different API base classes. Specifically, I'd like to make multiple different child classes that inherit from one of these different API-supplied base classes and the one Mixin, which will always have the Mixin initialization code executed in the same way, without code replication. However, it seems that the __init__ function of the Mixin class never gets called unless I explicitly call it in the Child class's __init__ function, which is less than ideal. I've built up a simple test case:

class APIBaseClassOne(object):
    def __init__(self, *args, **kwargs):
        print (" base ")

class SomeMixin(object):
    def __init__(self, *args, **kwargs):
        print (" mixin before ")
        super(SomeMixin, self).__init__(*args, **kwargs)
        print (" mixin after ")

class MyClass(APIBaseClassOne):
    pass

class MixedClass(MyClass, SomeMixin):
    pass

As you can see in the following output, the Mixin function's init never gets called:

>>> import test
>>> test.MixedClass()
 base
<test.MixedClass object at 0x1004cc850>

Is there a way to do this (have an init function in a Mixin get called) without writing every child class to explicitly invoke the Mixin's init function? (i.e., without having to do something like this in every class:)

class MixedClass(MyClass, SomeMixin):
    def __init__(*args, **kwargs):
        SomeMixin.__init__(self, *args, **kwargs)
        MyClass.__init__(self, *args, **kwargs) 

Btw, if all my child classes were inheriting from same base class, I realize I could create a new middle class that inherits from the base class and the mixin and keep it DRY that way. However, they inherit from different base classes with common functionality. (Django Field classes, to be precise).

2
  • 1
    In general, using multiple inheritance with base classes that weren't designed for it is a bad idea. Mix-in classes are usually designed together, and mixing-in arbitrary classes produces such messes. In any case, if both base classes each have an __init__ method, how should the interpreter know which one to call, or in which order to call them? Commented May 23, 2011 at 15:44
  • 2
    @André Caron: It could determine the order like C++ does, where base classes are initialized in declaration order. Commented May 23, 2011 at 16:05

4 Answers 4

63

Sorry I saw this so late, but

class MixedClass2(SomeMixin, MyClass):
    pass

>>> m = MixedClass2()
 mixin before 
 base 
 mixin after

The pattern @Ignacio is talking about is called cooperative multiple inheritance, and it's great. But if a base class isn't interested in cooperating, make it the second base, and your mixin the first. The mixin's __init__() (and anything else it defines) will be checked before the base class, following Python's MRO.

This should solve the general question, though I'm not sure it handles your specific use. Base classes with custom metaclasses (like Django models) or with strange decorators (like @martineau's answer ;) can do crazy things.

Sign up to request clarification or add additional context in comments.

2 Comments

Even if it was late, this boils it down nicely (although the other answers provide some really good teaching)-- ultimately, an understanding of Python MRO seems critical before embarking on any multiple-inheritance adventures, or unexpected things will happen. THanks!
Welcome! Absolutely right, it's unfortunate the the MRO is presented as an advanced topic :/
40

Have the base class invoke super().__init__() even though it is a subclass of object. That way all the __init__() methods will be run.

class BaseClassOne(object):
    def __init__(self, *args, **kwargs):
        super(BaseClassOne, self).__init__(*args, **kwargs)
        print (" base ")

9 Comments

Thanks! I didn't realize that calling super would cause all __init__() methods to be run. Unfortunately BaseClassOne is an API (Django) supplied base class (i've updated my question to reflect this) but this may get me started in the right direction.
It won't cause all of them to be run, but it will cause the next one to be run, even if it's in a mixin.
In a multiple-inheritance situation, all classes should use super() to find the next class to initialize.
@kindall: I think many have missed the point that the OP can't change BaseClassOne because it part of an API they have no control over.
@martineau: That fact was revealed only after I posted this answer. And that doesn't make this answer incorrect, just much less relevant for the situation.
|
30

Python performs no implicit calls to the __init__ methods of a class' super-class(es)—but it's possible to make it happen automatically. One way is by defining a metaclass for your mixed class(es) that creates or extends the mixed class' __init__ method so that it calls all the listed bases' __init__ functions in the order they were listed.

A second way is to do it is to use a class decorator—which is shown in the Edit section below.

Using a metaclass:

class APIBaseClassOne(object):  # API class (can't be changed)
    def __init__(self, *args, **kwargs):
        print('  APIBaseClassOne.__init__()')

class SomeMixin(object):
    def __init__(self, *args, **kwargs):
        print('  SomeMixin.__init__()')

class MixedClassMeta(type):
    def __new__(cls, name, bases, classdict):
        classinit = classdict.get('__init__')  # Possibly None.

        # Define an __init__ function for the new class.
        def __init__(self, *args, **kwargs):
            # Call the __init__ functions of all the bases.
            for base in type(self).__bases__:
                base.__init__(self, *args, **kwargs)
            # Also call any __init__ function that was in the new class.
            if classinit:
                classinit(self, *args, **kwargs)

        # Add the local function to the new class.
        classdict['__init__'] = __init__
        return type.__new__(cls, name, bases, classdict)

class MixedClass(APIBaseClassOne, SomeMixin):
    __metaclass__ = MixedClassMeta  # important
    # If exists, called after the __init__'s of all the direct bases.
    def __init__(self, *args, **kwargs):
        print('  MixedClass.__init__()')

print('MixedClass():')
MixedClass()

Output:

MixedClass():
  APIBaseClassOne.__init__()
  SomeMixin.__init__()
  MixedClass.__init__()

Edit

Here's how to accomplish the same thing with a class decorator (requires Python 2.6+):

class APIBaseClassOne(object):  # API class (can't be changed)
    def __init__(self, *args, **kwargs):
        print('  APIBaseClassOne.__init__()')

class SomeMixin(object):
    def __init__(self, *args, **kwargs):
        print('  SomeMixin.__init__()')

def mixedomatic(cls):
    """ Mixed-in class decorator. """
    classinit = cls.__dict__.get('__init__')  # Possibly None.

    # Define an __init__ function for the class.
    def __init__(self, *args, **kwargs):
        # Call the __init__ functions of all the bases.
        for base in cls.__bases__:
            base.__init__(self, *args, **kwargs)
        # Also call any __init__ function that was in the class.
        if classinit:
            classinit(self, *args, **kwargs)

    # Make the local function the class's __init__.
    setattr(cls, '__init__', __init__)
    return cls

@mixedomatic
class MixedClass(APIBaseClassOne, SomeMixin):
    # If exists, called after the __init__'s of all the direct base classes.
    def __init__(self, *args, **kwargs):
        print('  MixedClass.__init__()')

print('MixedClass():')
MixedClass()

Notes

For Python < 2.6, use MixedClass = mixedomatic(MixedClass) following the class definition.

In Python 3 the syntax for specifying metaclasses is different, so instead of the:

class MixedClass(APIBaseClassOne, SomeMixin):
    __metaclass__ = MixedClassMeta  # important

shown above, you would need to use:

class MixedClass(APIBaseClassOne, SomeMixin, metaclass=MixedClassMeta):

The class decorator version will work as-is in both versions.

13 Comments

Thanks for demonstrating this. While the discussion in the other answer has made me rethink whether its a good idea to begin with, this implementation works and I've learned a lot.
@Ben: Yes, mixins are somewhat controversial, and even considered harmful by some, plus using them in Python is somewhat complicated by the fact that it doesn't call base class constructors by default (unlike, say, C++ would). However their use may be justified in a situation like yours where you can't modify the API's class -- but you might want to think more about alternatives to do what you need like subclassing or wrapping the API's class instead. Another possibility might be to make an instance of it an attribute of one of your own classes.
not sure, wish i could accept both, but yours has been re-accepted :)
@Ben: Thanks. I can understand your dilemma, but my [biased] opinion is that mine is better because not only does it actually solve your particular problem, it manages to do so in a generic way -- and is thus a little more complicated than just calling the base class's init(). Real world problems can be like that.
I really like the mixedomatic decorator. I modified it slightly: classinit = cls.__init__ if '__init__' in cls.__dict__ else None
|
2

We can learn much from all answers. But I think only Ignacio's answers is CORRECT here.

In other words and very simply said:

If you rewrite some method, always add super() call inside !!! Any method without super() call will break the run of such method for classes/mixins which follows !!!

So the Ignacio's answer is the proper one for the case if WE create the classes.

Problem is that the 3RD PARTY authors write classes which are buggy in this manner. I have found this stackoverflow question while I was working with Django Rest Framework.

class FinalViewSet(viewsets.ModelViewSet, MyMixin)

will fail (the __init__ from MyMixin will not run), while

class FinalViewSet(MyMixin, viewsets.ModelViewSet)

will make things correct.

So lets go to sources of viewsets.ModelViewSet ...

... and we will find that django.views.generic.base.View has incorrect __init__ method.

So the problems is not at DRF authors but at Django authors, in this case.

1 Comment

You should probably raise that as an issue, although its possible theres some dark magic going on with that library that necesitates it breaking things.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.