Last time (Implement a model with states and behaviors (2)), we implemented StopWatch
with three states in two ways. .. This time, implement StopWatch
using * decorator *.
First, describe the state transition with * decorator *. It becomes easy to understand whether it is a method that transitions to a state.
from enum import auto
def transit(state):
def decorator(func):
def inner(self, *args, **kwargs):
self.start_stop, self.reset = self._TRANSIT[state]
func(self, *args, **kwargs)
return inner
return decorator
class StopWatch:
WAIT, PAUSE, MEASURE = auto(), auto(), auto()
def __init__(self):
self._TRANSIT = {StopWatch.WAIT: (self.start, lambda *args: None),
StopWatch.PAUSE: (self.start, self.reset_time),
StopWatch.MEASURE: (self.pause, lambda *args: None)}
self._TRANSIT_REVERSED = {v: k for k, v in self._TRANSIT.items()}
self.start_stop, self.reset = self._TRANSIT[StopWatch.WAIT]
@property
def state(self):
return self._TRANSIT_REVERSED[self.start_stop, self.reset]
@transit(MEASURE)
def start(self):
pass
@transit(PAUSE)
def pause(self):
pass
@transit(WAIT)
def reset_time(self):
pass
Furthermore, try cutting out the state transition table with a decorator. The behavior
decorator represents the behavior in a certain state.
from enum import auto
from state_machine import behavior, transit
class StopWatch:
WAIT, MEASURE, PAUSE = auto(), auto(), auto()
def __init__(self):
self.state = StopWatch.WAIT
@behavior(WAIT, "start")
@behavior(MEASURE, "pause")
@behavior(PAUSE, "start")
def start_stop(self):
pass
@behavior(PAUSE, "reset_time")
def reset(self):
pass
@transit(MEASURE)
def start(self):
pass
@transit(PAUSE)
def pause(self):
pass
@transit(WAIT)
def reset_time(self):
pass
state_machine.py
from functools import wraps
def transit(state):
def decorator(func):
@wraps(func)
def inner(self, *args, **kwargs):
self.state = state
func(self, *args, **kwargs)
return inner
return decorator
def behavior(state, function):
def decorator(func):
@wraps(func)
def inner(self, *args, **kwargs):
if self.state == state:
getattr(self, function)(*args, **kwargs)
else:
func(self, *args, **kwargs)
return inner
return decorator
It is assumed that the state is kept in self.state, but since the same * decorator * can be used in other models, it was cut out as a module state_machine
.
At the timing of processing the decorator, the function is still unbound, so the behavior is given as a string. You can also use the function __name__
to call a bound method at runtime. (Only applicable parts are shown below.)
Note that if you have * decorator * that doesn't use functools.wrap ()
, it won't work as expected. (* decorator * doesn't rewrite the function __name__
when usingfunctools.wrap ()
.)
state_machine.py
def behavior(state, function):
...
if self.state == state:
getattr(self, function.__name__)(*args, **kwargs)
...
...
@behavior(PAUSE, reset_time)
def reset(self):
pass
...
Recommended Posts