Python decorator to determine order of execution of methods

こ雲淡風輕ζ 提交于 2020-01-15 10:11:33

问题


I have a basic class Framework with 3 methods that can be set by the user: initialize, handle_event and finalize.

These methods are executed by the method 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()

I would like to create 3 decorators: on_initialize, on_event and on_finalize so that I could write such a class:

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()

These decorators determine the order of execution of the optional methods the user may add to the class. I think that by default, initialize, handle_event and finalize should take precedence=0. Then the user could add any method with the right decorator and he will know when they get executed in the simulation run.

I have honestly no idea how to get started with this problem. Any help to push me in the right direction will be very welcome! Many thanks.


回答1:


If you are using Python 3.6, this is a case that can take advantage of the new __init_subclass__ method. It is called on the superclass by subclasses when they are created.

Withut Python3.6 you have to resort to a metaclass.

The decorator itself can just mark each method with the needed data.

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()

If you will have a complex class hierarchy that should inherit the action methods properly, you wll have to merge the lists in the handlers dictionary with the ones in the superclass (get the superclass as cls.__mro__[1]) before applying then as class attributes.

Also, if you are using any Python < 3.6, you will need to move the logic on __init_subclass__ to a metaclass. Just put the code as it is on the __init__ method of a metaclass (and adjust the incoming parameters and super call as apropriate), and it should work just the same.




回答2:


My idea is to use class based decorators, which are simple and gives intermediate context to share between decorated functions. So decorator would look like this (I am using 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

on decoration, first of all init is executed and it stores the precedence value for further use. Secondly the call is executed which just appends target function to the methods dictionary (Please note that call and methods structure could be customized to allow calling multiple methods with same precedence).

on the other hand, target class and methods would look like this

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")

which ensures that, if one of the following methods are called, it will call all the decorated methods with given order.

target = Target()
target.initialize()

Hope it helps, please comment me below if you were interested in something other.



来源:https://stackoverflow.com/questions/47054796/python-decorator-to-determine-order-of-execution-of-methods

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!