13

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?

codeforester
  • 39,467
  • 16
  • 112
  • 140
kortina
  • 5,821
  • 4
  • 24
  • 28
  • 1
    The short answer is that you can't. If it's overriden, the member lookup algorithm is required to return the overriden version. A more robust approach would be a design that doesn't require overriding of such vital parts, though that's not always possible. Alternatively, write your code such that it raises and error or produces obviously wrong results when your version is not called, to aid debugging. –  Nov 01 '11 at 17:20
  • @delnan: Using metaclasses wouldn't be implementation-dependent (but might well be considered a hack, at least if used for this purpose). – Sven Marnach Nov 01 '11 at 17:21
  • @Sven: I could have sworn there's a "maybe", specifically because I don't trust myself to enumerate all possibilities. But I revised my comment anyway. –  Nov 01 '11 at 17:23
  • @Sven I don't think metaclasses will be of any help here. – Lachezar Nov 01 '11 at 17:23
  • @Lucho: Well, I can see where he's going - see if subclasses overrides the method, if so wrap it with a version that calls the base class version first. Not perfect (can be overriden again after class creation, can lead to people calling the base class implementation manually though it's already done for them), but moderately robust. –  Nov 01 '11 at 17:25

5 Answers5

16

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
j2labs
  • 184
  • 4
4

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.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • This works fine as long as the hierarchy doesn't get any deeper than one level, but given that it seems to be the best solution. – Sven Marnach Nov 01 '11 at 17:31
  • If telling users to define a special method is fine, then telling them to simply call the base method when overriding it would be just as fine as well. And you wouldn’t introduce other problems like that. – poke Nov 01 '11 at 17:36
  • Although this works, I should have specified that I wanted people subclassing to be able to use the name prepare instead of prepare_hook. The actual use case is that my BaseHandler is subclassing a tornado RequestHandler. Since developers may be familiar with using the prepare method when writing tornado handlers, I would like them to be able to use the same method name even when they inherit from my BaseHandler class. The metaclass solution fits my use case better. – kortina Nov 01 '11 at 18:15
3

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.

poke
  • 369,085
  • 72
  • 557
  • 602
2

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
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • Note that you are already overriding `BaseHandler.__init__` with your subclass, so you end up with a AttributeError… – poke Nov 01 '11 at 17:34
  • @poke: Thanks, fixed. :) Well, now I have somehow moved the problem to `__init__()`… – Sven Marnach Nov 01 '11 at 17:35
  • 1
    You could instead move the initialization of the list to the class body (i.e. before `__init__`, outside of any method), or you could add an existence test into the register method, which initializes it if necessary. – poke Nov 01 '11 at 17:38
  • @poke: I like both suggestions, especially the first one. And I like your answer as well. – Sven Marnach Nov 01 '11 at 17:47
0

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

Community
  • 1
  • 1
Lachezar
  • 6,523
  • 3
  • 33
  • 34