Python装饰器确定方法的执行顺序

时间:2017-11-01 12:24:40

标签: python decorator

我有一个基本类Framework,其中有3种方法可由用户设置:initializehandle_eventfinalize

这些方法由方法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_initializeon_eventon_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()

这些装饰器确定用户可以添加到类中的可选方法的执行顺序。我认为默认情况下,initializehandle_eventfinalize应该precedence=0。然后,用户可以使用正确的装饰器添加任何方法,并且他将知道它们何时在模拟运行中执行。

我真的不知道如何开始解决这个问题。任何帮助推动我朝着正确方向前进的帮助都将非常受欢迎!非常感谢。

2 个答案:

答案 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__方法上(并调整传入的参数和超级调用为适当的),它应该工作正常。

答案 1 :(得分:0)

我的想法是使用基于类的装饰器,它很简单,并提供在装饰函数之间共享的中间上下文。所以decorator看起来像这样(我使用的是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()

希望它有所帮助,如果您对其他内容感兴趣,请在下面给我评论。