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