问题
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