Python-декоратор для определения порядка выполнения методов

У меня есть базовый класс Framework с 3 методами, которые могут быть установлены пользователем: initialize, handle_event а также finalize,

Эти методы выполняются методом run:

class Framework(object):

    def initialize(self):
        pass

    def handle_event(self, event):
        pass

    def finalize(self):
        pass 

    def run(self):
        self.initialize()

        for event in range(10):
            self.handle_event(event)

        self.finalize()

Я хотел бы создать 3 декоратора: on_initialize, on_event а также on_finalize чтобы я мог написать такой класс:

class MyFramework(Framework):

    # The following methods will be executed once in this order
    @on_initialize(precedence=-1)
    def say_hi(self):
        print('say_hi')

    @on_initialize(precedence=0)
    def initialize(self):
        print('initialize')

    @on_initialize(precedence=1)
    def start_processing_events(self):
        print('start_processing_events')

    # The following methods will be executed in each event in this order
    @on_event(precedence=-1)
    def before_handle_event(self, event):
        print('before_handle_event:', event)

    @on_event(precedence=0)
    def handle_event(self, event):
        print('handle_event:', event)

    @on_event(precedence=1)
    def after_handle_event(self, event):
        print('after_handle_event:', event)

    # The following methods will be executed once at the end on this order
    @on_finalize(precedence=-1)
    def before_finalize(self):
        print('before_finalize')

    @on_finalize(precedence=0)
    def finalize(self):
        print('finalize')

    @on_finalize(precedence=1)
    def after_finalize(self):
        print('after_finalize')

if __name__ == '__main__':
    f = MyFramework()
    f.run()

Эти декораторы определяют порядок выполнения дополнительных методов, которые пользователь может добавить в класс. Я думаю, что по умолчанию, initialize, handle_event а также finalize должен взять precedence=0, Затем пользователь может добавить любой метод с подходящим декоратором, и он будет знать, когда они будут выполнены во время симуляции.

Я, честно говоря, понятия не имею, как начать с этой проблемой. Любая помощь, чтобы подтолкнуть меня в правильном направлении, будет очень кстати! Большое спасибо.

2 ответа

Решение

Если вы используете Python 3.6, это тот случай, когда можно воспользоваться преимуществами нового __init_subclass__ метод. Он вызывается в суперклассе подклассами при их создании.

Без Python3.6 вам придется прибегнуть к метаклассу.

Сам декоратор может просто пометить каждый метод необходимыми данными.

def on_initialize(precedence=0):
    def marker(func):
        func._initializer = precedence
        return func
    return marker

def on_event(precedence=0):
    def marker(func):
        func._event_handler = precedence
        return func
    return marker


def on_finalize(precedence=0):
    def marker(func):
        func._finalizer = precedence
        return func
    return marker



class Framework:

    def __init_subclass__(cls, *args, **kw):
        super().__init_subclass__(*args, **kw)
        handlers = dict(_initializer=[], _event_handler=[], _finalizer=[])
        for name, method in cls.__dict__.items():
            for handler_type in handlers:
                if hasattr(method, handler_type):
                    handlers[handler_type].append((getattr(method, handler_type), name))

        for handler_type in handlers:
            setattr(cls, handler_type, 
                    [handler[1] for handler in sorted(handlers[handler_type])])

    def initialize(self):
        for method_name in self._initializer:
            getattr(self, method_name)()

    def handle_event(self, event):
        for method_name in self._event_handler:
            getattr(self, method_name)(event)

    def finalize(self):
        for method_name in self._finalizer:
            getattr(self, method_name)()

    def run(self):
        self.initialize()

        for event in range(10):
            self.handle_event(event)

        self.finalize()

Если у вас будет сложная иерархия классов, которая должна правильно наследовать методы действия, вам придется объединить списки в handlers словарь с теми в суперклассе (получить суперкласс как cls.__mro__[1]) перед применением затем в качестве атрибутов класса.

Кроме того, если вы используете какой-либо Python < 3.6, вам нужно будет переместить логику на __init_subclass__ метаклассу. Просто поместите код, как он есть на __init__ метод метакласса (и настройте входящие параметры и супер-вызовы соответствующим образом), и он должен работать точно так же.

Моя идея состоит в том, чтобы использовать декораторы на основе классов, которые просты и дают промежуточный контекст для разделения между декорированными функциями. Таким образом, декоратор будет выглядеть так (я использую python3.5):

class OnInitialize:
  methods = {}

  def __init__(self, precedence):
    self.precedence = precedence

  def __call__(self, func):
    self.methods[self.precedence] = func
    def wrapper(*a, **kw):
      for precedence in sorted(self.methods.keys()):
        self.methods[precedence](*a, **kw)
    return wrapper

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

с другой стороны, целевой класс и методы будут выглядеть так

class Target:
  @OnInitialize(precedence=-1)
  def say_hi(self):
    print("say_hi")

  @OnInitialize(precedence=0)
  def initialize(self):
    print("initialize")

  @OnInitialize(precedence=1)
  def start_processing_events(self):
    print("start_processing_events")

что гарантирует, что при вызове одного из следующих методов он вызовет все декорированные методы с заданным порядком.

target = Target()
target.initialize()

Надеюсь, это поможет, пожалуйста, прокомментируйте меня ниже, если вы заинтересованы в чем-то другом.

Другие вопросы по тегам