kortina
kortina

Reputation: 6101

How can I ensure that one of my class's methods is always called even if a subclass overrides it?

For example, I have a

class BaseHandler(object):
    def prepare(self):
        self.prepped = 1

I do not want everyone that subclasses BaseHandler and also wants to implement prepare to have to remember to call

super(SubBaseHandler, self).prepare()

Is there a way to ensure the superclass method is run even if the subclass also implements prepare?

Upvotes: 14

Views: 2138

Answers (5)

j2labs
j2labs

Reputation: 184

I have solved this problem using a metaclass.

Using a metaclass allows the implementer of the BaseHandler to be sure that all subclasses will call the superclasses prepare() with no adjustment to any existing code.

The metaclass looks for an implementation of prepare on both classes and then overwrites the subclass prepare with one that calls superclass.prepare followed by subclass.prepare.

class MetaHandler(type):
    def __new__(cls, name, bases, attrs):
        instance = type.__new__(cls, name, bases, attrs)
        super_instance = super(instance, instance)
        if hasattr(super_instance, 'prepare') and hasattr(instance, 'prepare'):
            super_prepare = getattr(super_instance, 'prepare')
            sub_prepare = getattr(instance, 'prepare')
            def new_prepare(self):
                super_prepare(self)
                sub_prepare(self)
            setattr(instance, 'prepare', new_prepare)
        return instance


class BaseHandler(object):
    __metaclass__ = MetaHandler
    def prepare(self):
        print 'BaseHandler.prepare'


class SubHandler(BaseHandler):
    def prepare(self):
        print 'SubHandler.prepare'

Using it looks like this:

>>> sh = SubHandler()
>>> sh.prepare()
BaseHandler.prepare
SubHandler.prepare

Upvotes: 16

poke
poke

Reputation: 387707

Just accept that you have to tell people subclassing your class to call the base method when overriding it. Every other solution either requires you to explain them to do something else, or involves some un-pythonic hacks which could be circumvented too.

Python’s object inheritance model was designed to be open, and any try to go another way will just overcomplicate the problem which does not really exist anyway. Just tell everybody using your stuff to either follow your “rules”, or the program will mess up.

Upvotes: 3

unutbu
unutbu

Reputation: 879621

Tell your developers to define prepare_hook instead of prepare, but tell the users to call prepare:

class BaseHandler(object):
    def prepare(self):
        self.prepped = 1
        self.prepare_hook()
    def prepare_hook(self):
        pass

class SubBaseHandler(BaseHandler):
    def prepare_hook(self):
        pass

foo = SubBaseHandler()
foo.prepare()

If you want more complex chaining of prepare calls from multiple subclasses, then your developers should really use super as that's what it was intended for.

Upvotes: 4

Sven Marnach
Sven Marnach

Reputation: 601679

One explicit solution without too much magic going on would be to maintain a list of prepare call-backs:

class BaseHandler(object):
    def __init__(self):
        self.prepare_callbacks = []
    def register_prepare_callback(self, callback):
        self.prepare_callbacks.append(callback)
    def prepare(self):
        # Do BaseHandler preparation
        for callback in self.prepare_callbacks:
            callback()

class MyHandler(BaseHandler):
    def __init__(self):
        BaseHandler.__init__(self)
        self.register_prepare_callback(self._prepare)
    def _prepare(self):
        # whatever

Upvotes: 2

Lachezar
Lachezar

Reputation: 6703

In general you can try using __getattribute__ to achive something like this (until the moment someone overwrites this method too), but it is against the Python ideas. There is a reason to be able to access private object members in Python. The reason is mentioned in import this

Upvotes: 0

Related Questions