I'm building a Python class where I want to mark some of the instance methods as 'handlers' that will be applied when the instance is told to run. Below is a minimal working example.
_handlers = []
def handler_marker(marked_fun):
_handlers.append(marked_fun)
return marked_fun
class ManyHandler:
def __init__(self):
pass
def run(self, input):
for handling_fun in _handlers:
handling_fun(self, input)
@handler_marker
def method_1(self, input):
print(input)
@handler_marker
def method_2(self, input):
print(input[::-1])
def method_3(self, input):
print("I'm not marked.")
if __name__ == "__main__":
MH = ManyHandler()
MH.run('Test string')
This works, but I don't like that the _handlers
list and the decorator are outside of the class definition. Especially the list of handlers would feel a lot more natural as a class variable here. However, if we just move the _handlers
inside the class and change the decorator to
def handler_marker(marked_fun):
ManyHandler._handlers.append(marked_fun)
return marked_fun
we face one of two problems:
NameError: name 'ManyHandler' is not defined
since the decorator doesn't know yet what the ManyHandler will be.NameError: name 'handler_marker' is not defined
since the class fails to set itself up without knowing what the decorator here is.So I would like to put the decorator inside of the class as well. But to access the class variable _handlers
, the decorator would need to have a reference to the ManyHandler
class. We can't pass the class to the decorator as an argument since at the time it decorator is being first applied, we're in the process of still building the class.
So far the only reasonable solution I've found has been to set up a new class to handle the handlers:
class HandlerHandler:
_handlers = []
def __init__(self):
pass
@classmethod
def handler_marker(cls, marked_fun):
cls._handlers.append(marked_fun)
return marked_fun
class ManyHandler:
MetaHandler = HandlerHandler
_handlers = MetaHandler._handlers
handler_marker = MetaHandler.handler_marker
def __init__(self):
pass
def run(self, input):
for handling_fun in type(self)._handlers:
handling_fun(self, input)
@handler_marker
def method_1(self, input):
print(input)
@handler_marker
def method_2(self, input):
print(input[::-1])
def method_3(self, input):
print("I'm not marked.")
if __name__ == "__main__":
MH = ManyHandler()
MH.run('Test string')
It works, but I'm not convinced if this is Pythonic (or even sane) anymore.
So my question is:
What would be the most Pythonic way to do this?
Creating a separate class is totally sane -- that's what classes are for, for encapsulating state and behavior. However, here's how I would clean up this approach:
class HandlerRegistry:
def __init__(self):
self._handlers = []
def register(self, fun):
self._handlers.append(fun)
return fun
def __iter__(self):
yield from self._handlers
class ManyHandler:
handlers = HandlerRegistry()
def run(self, input):
for handling_fun in self.handlers:
handling_fun(self, input)
@handlers.register
def method_1(self, input):
print(input)
@handlers.register
def method_2(self, input):
print(input[::-1])
def method_3(self, input):
print("I'm not marked.")
mh = ManyHandler()
mh.run('Test string')