How to call super of enclosing class in a mixin in Python?

Question:

I have the following code, in Django:

class Parent(models.Model):
    def save(self):
        # Do Stuff A

class Mixin(object):
    def save(self):
        # Do Stuff B

class A(Parent, Mixin):
    def save(self):
        super(A, self).save()
        # Do stuff C

Now, I want to use the mixin without blatting the behaviour of the save in Parent. So I when I save, I want to do stuff C, B, and A. I’ve read Calling the setter of a super class in a mixin however I don’t get it and having read the super docs it doesn’t seem to answer my question.

THe question is, what do I put in the mixin to make sure it does stuff B and doesn’t stop Stuff A from happening?

Asked By: David Boshton

||

Answers:

The best practice for calling the implementation from the superclass is to use super():

class Mixin(object):
    def save(self):
        super(Mixin, self).save()
        # Do Stuff B here or before super call, as you wish

What is important is that you call super() in each class (so that it propagates all the way) but not the topmost (base) class, because its superclass does not have save().

Note that when you call super(Mixin, self).save(), you don’t really know what the super class would be once it is executed. That will be defined later.

Unlike some other languages, in python, the end class will always have a linear list of classes from which it inherits. That is called MRO (Method Resolution Order). From MRO Python decides what to do on super() call. You can see what the MRO is for your class this way:

>>> A.__mro__
(<class '__main__.A'>, <class '__main__.Parent'>, <class '__main__.Model'>, <class '__main__.Mixin'>, <type 'object'>)

So, A‘s super is Parent, Parent‘s super is Model, Model‘s super is Mixin and Mixin‘s super is object.

That is wrong, so you should change A‘s parents to:

class A(Mixin, Parent):

Then you’d have a better MRO:

>>> A.__mro__
(<class '__main__.A'>, <class '__main__.Mixin'>, <class '__main__.Parent'>, <class '__main__.Model'>, <type 'object'>)
Answered By: zvone

How about calling super in your mixin class?

class Parent(object):
    def test(self):
        print("parent")


class MyMixin(object):
    def test(self):
        super(MyMixin, self).test()
        print("mixin")


class MyClass(MyMixin, Parent):
    def test(self):
        super(MyClass, self).test()
        print("self")

if __name__ == "__main__":
    my_obj = MyClass()
    my_obj.test()

This will give you the output as:

$ python test.py
parent
mixin
self
Answered By: Click2Death

@Click2Death answer is correct, however, when you call super().test() inside your mixin class most IDE will claim that test is unresolved, which is correct.

enter image description here

Here is how to make your IDE happy and your code better.

class Parent(object):
    def test(self):
        print("parent")


class MyMixin(object):
    def test(self):
        super_test = getattr(super(), 'test')
        if super_test and callable(super_test):
            super_test()
        print("mixin")


class MyClass(MyMixin, Parent):
    def test(self):
        super().test()
        print("self")

if __name__ == "__main__":
    my_obj = MyClass()
    my_obj.test()

This is Python 3 code, to make it working with Python 2 you need to pass two arguments to the super(MyClass, self) call

Answered By: Vladimir Prudnikov

Django Example (Python 3+)

To expand on Vladimir Prudnikov’s answer in a Django context, the template view mixin class could be structured as shown below.

from django.views.generic.base import View


class MyViewMixin:

    def dispatch(self, request, *args, **kwargs):
        super_dispatch = getattr(super(), 'dispatch')
        if super_dispatch and callable(super_dispatch):
            return super_dispatch(request, *args, **kwargs)
        raise RuntimeError('MyViewMixin must be used as part of a '
            'multiple inheritance chain that includes a Django template-view')


class MyCustomView(MyViewMixin, View):

    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)
Answered By: Christopher Peisert
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.