В python, как я могу гарантировать, что один из моих методов класса всегда вызывается, даже если подкласс отменяет его?

Например, у меня есть

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

Я не хочу, чтобы все, что подклассы BaseHandler, а также хотят реализовать, готовы не забыть позвонить

super(SubBaseHandler, self).prepare()

Есть ли способ гарантировать, что метод суперкласса выполняется, даже если подкласс также реализует подготовку?

Ответ 1

Я решил эту проблему, используя метакласс.

Использование метакласса позволяет разработчику BaseHandler быть уверенным, что все подклассы вызовут суперклассы prepare() без настройки на любой существующий код.

Метакласс ищет реализацию prepare для обоих классов, а затем перезаписывает подкласс, готовый к тому, который вызывает superclass.prepare, за которым следует 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'

Использование выглядит так:

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

Ответ 2

Сообщите разработчикам определить prepare_hook вместо prepare, но попросите пользователей вызвать 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()

Если вам требуется более сложная цепочка вызовов prepare из нескольких подклассов, то ваши разработчики должны действительно использовать super как то, для чего он предназначался.

Ответ 3

Просто примите, что вы должны сказать людям, подклассифицирующим ваш класс, чтобы вызвать базовый метод при его переопределении. Каждое другое решение требует, чтобы вы объяснили, что они делают что-то еще, или связаны с некоторыми непифовыми хаками, которые тоже можно обойти.

Модель наследования объектов Pythons была разработана как открытая, и любая попытка пойти другим путем просто перекомплементирует проблему, которая на самом деле не существует. Просто скажите всем, используя ваши вещи, либо следовать вашим "правилам", либо программа испортится.

Ответ 4

Одно явное решение без слишком большой магии будет состоять в том, чтобы сохранить список подготовительных обратных вызовов:

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

Ответ 5

В общем, вы можете попробовать использовать __getattribute__, чтобы достичь чего-то подобного (до тех пор, пока кто-то не перезапишет этот метод), но это противоречит идеям Python. Существует причина для доступа к частным объектам в Python. Причина упоминается в import this