Scott Stafford
Scott Stafford

Reputation: 44776

Python mixin with a method that cannot be overridden (by default)?

In Python, I am using a framework that lets me hook into a lifecycle like so:

class MyView(BaseView):
    def pre_save(self):
        print "view presave"

I want to write a mixin to do something in pre_save, but I already have lots of code in lots of classes using pre_save as above, without calling super(). If I add in my mixin like so:

class MyMixin(object):
    def pre_save(self):
        print "mixin presave"

class MyView(MyMixin, BaseView):
    def pre_save(self):
        print "view presave"

It naturally overwrites the mixin, and prints "view presave" only. Is there another, sneakier way I can write MyMixin to not force all client views to remember to call super()?

Upvotes: 0

Views: 1019

Answers (1)

George C
George C

Reputation: 170

This answer is a proof of concept, not a recommendation. DO NOT DO THIS IN PRODUCTION CODE. You will create maintenance nightmare. I agree with those commenting on your question that the only safe approach is editing the derived classes.

That said, it is technically possible using a metaclass.

def do_evil(self, method):
    print "doing evil"
    MyMixin.pre_save(self)
    method(self)

class ConcentratedEvil(type):
    tainted_classes = ['MyView'] # Add others as needed
    def __new__(mcs, name, bases, dict):
        if name in mcs.tainted_classes:
            print "tainting {0} with the power of evil".format(name)
            old_method = dict['pre_save']
            dict['pre_save'] = lambda x: do_evil(x, old_method)
            print dict.keys()
        return type.__new__(mcs, name, bases, dict)

class BaseView(object):
    pass

class MyMixin(object):
    __metaclass__ = ConcentratedEvil
    def pre_save(self):
        print "mixin presave"

class MyView(MyMixin, BaseView):
    def pre_save(self):
        print "view presave"

At which point you can do:

>>> view = MyView()
>>> view.pre_save()
doing evil
mixin presave
view presave
>>>

I think this only works if MyMixin is at the front of the base class list.

Upvotes: 1

Related Questions