I building SPA on Django and I have one huge function with many if
statement for checking state name of my object field. Like this:
if self.state == 'new':
do some logic
if self.state == 'archive':
do some logic
and so on. I reading nice book "Fluent python" now, and I mention about @singledispatch
decorator, it looks so great, but it can overide function only with diferent type of parametres like str
, int
, etc.
Question is, if there in python or Django way to separate logic like in my huge function with overided function like singledispatch
do?
There is, though you have to write it. One possibility is to create a descriptor that does the dispatching based on instance.state
or any chosen state_attr
:
class StateDispatcher(object):
def __init__(self, state_attr='state'):
self.registry = {}
self._state_attr = state_attr
def __get__(self, instance, owner):
if instance is None:
return self
method = self.registry[getattr(instance, self._state_attr)]
return method.__get__(instance, owner)
def register(self, state):
def decorator(method):
self.registry[state] = method
return method
return decorator
https://docs.python.org/3/howto/descriptor.html#functions-and-methods:
To support method calls, functions include the
__get__()
method for binding methods during attribute access. This means that all functions are non-data descriptors which return bound or unbound methods depending whether they are invoked from an object or a class.
In your stateful class you can then create a dispatcher and register methods:
class StateMachine(object):
dispatcher = StateDispatcher()
state = None
@dispatcher.register('test')
def test(self):
print('Hello, World!', self.state)
@dispatcher.register('working')
def do_work(self):
print('Working hard, or hardly working?', self.state)
Let's see it in action:
>>> sm = StateMachine()
>>> sm.state = 'test'
>>> sm.dispatcher()
Hello, World! test
>>> sm.state = 'working'
>>> sm.dispatcher()
Working hard, or hardly working? working
>>> sm.state = None
>>> sm.dispatcher()
Traceback (most recent call last):
...
File "dispatcher.py", line 11, in __get__
method = self.registry[getattr(instance, self._state_attr)]
KeyError: None
Note that this is a quite evil method of dispatching based on state, since for future readers of your code the whole mechanism will be hard to follow.
Another method of dispatching on textual state is to encode the state in your method names and choose the correct method based on that in a dispatching function. Many python classes use this pattern (ast.NodeVisitor
for example):
class StateMachine(object):
def dispatch(self, *args, **kwgs):
getattr(self, 'do_{}'.format(self.state))(*args, **kwgs)
def do_new(self):
print('new')
def do_archive(self):
print('archive')
sm = StateMachine()
sm.state = 'new'
sm.dispatch()
sm.state = 'archive'
sm.dispatch()