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()
Надеюсь, это поможет, пожалуйста, прокомментируйте меня ниже, если вы заинтересованы в чем-то другом.