Переходы в конечном автомате, управляемом событиями - PullRequest
0 голосов
/ 29 октября 2018

Я использую Переходы в python для реализации событий с конечным автоматом, управляемых событиями.

Я хочу реализовать FSM, в котором переход инициируется событием, которое обрабатывается, избегая оператора if / elif.

Например: у меня есть простой FSM лампы, если оно пришло (недетерминированным образом), то событие 'goInlampOn' переходит в состояние «включено», если наступает событие 'goInlampOff', мы переходим к состояние 'off'.

Итак, мой вопрос:

Есть ли способ создать метод с именем process_event(event) в классе FSM, который обрабатывает каждое событие с помощью логики моего FSM без использования очень длинного (не в этом очень простом случае) оператора if-elif?

Вот пример кода:

class MyFsm(object):

    transitions = [
        {'trigger': 'switchOff', 'source':'on', 'dest':'off'},
        {'trigger': 'switchOn', 'source':'off', 'dest':'on'}
    ]

    def __init__(self):
        ''' initialating the FSM '''
        self.machine = Machine(model=self, states=self.states,
                               transitions=self.transitions, initial='on')
        on = On()
        off = Off()
        self.machine.add_state(off)
        self.machine.add_state(on)

    # A SMART SOLUTION TO IMPLEMENT THIS METHOD WITHOUT USING
    # if state == off: ... elif state==on...
    def process_event(self,event):

         if self.state == 'off':
             if event == 'goInlampOn':
                 self.switchOn()

         elif self.state == 'on':
             if event == 'goInlampOff':
                 self.switchOff()

в случае, который я представил, есть только 2 состояния, но если у меня было 10 или 15? Я попытался реализовать решение, которое мне кажется @aleneum. однако в этом решении я должен дважды записать переходы моего конечного автомата. это правильно ? может ли быть лучшее решение (избегая записи переходов дважды)? это конечный автомат с 4 состояниями (A, B, C, D), разрешены только следующие переходы:
1. A-> B
2. B-> C
3. C-> D
4. D-> A
5. C-> A
здесь код:

from transitions import Machine
from states import A,B,C,D
class MyFsm(object):

transitions = [
    {'trigger': 'go_in_B_fromA','source':'A','dest':'B'},
    {'trigger': 'go_in_C_fromB','source':'B','dest':'C'},
    {'trigger': 'go_in_D_fromC','source':'C','dest':'D'},
    {'trigger': 'go_in_A_fromD','source':'D','dest':'A'},
    {'trigger': 'go_in_A_fromC','source':'C','dest':'A'},        
    {'trigger': 'go_in_B_fromA','source':['C','D','B'],'dest':None},
    {'trigger': 'go_in_C_fromB','source':['C','D','A'],'dest':None},
    {'trigger': 'go_in_D_fromC','source':['B','D','A'],'dest':None},
    {'trigger': 'go_in_A_fromD','source':['B','A','C'],'dest':None},
    {'trigger': 'go_in_A_fromC','source':['D','A','B'],'dest':None}
]

def __init__(self):   
    self.machine = Machine(model=self, states = self.states ,transitions= self.transitions, initial = 'A' )       
    a = A()
    b = B()
    c = C()
    d = D()       
    self.machine.add_state(a)
    self.machine.add_state(b)
    self.machine.add_state(c)
    self.machine.add_state(d)


def process_event(self,event):   

    if event == 'go_in_B_fromA' :
        self.go_in_B_fromA()

    if event == 'go_in_C_fromB' :
        self.go_in_C_fromB()

    if event == 'go_in_D_fromC' :
        self.go_in_D_fromC()

    if event == 'go_in_A_fromD' :
        self.go_in_A_fromD()

    if event == 'go_in_A_fromC' :
        self.go_in_A_fromC()

'''my main is something like this'''
myfsm = MyFsm()
while True:
    event = event_from_external_bahaviour()
    myfsm.process_event(event)

Ответы [ 2 ]

0 голосов
/ 29 октября 2018

Как уже упоминалось @martineau, влияние событий / поведение модели должно определяться ее текущим состоянием.Большие блоки операторов if / then - это то, чего вы хотите избежать, используя конечные автоматы.Взгляните на следующий фрагмент кода:

from transitions.core import Machine, State, MachineError

class On(State):

    def __init__(self, *args, **kwargs):
        super(On, self).__init__(*args, **kwargs)

class Off(State):

    def __init__(self, *args, **kwargs):
        super(Off, self).__init__(*args, **kwargs)


class MyFsm(object):

    transitions = [
        {'trigger': 'switchOff', 'source':'on', 'dest':'off'},
        {'trigger': 'switchOn', 'source':'off', 'dest':'on'}
    ]

    def __init__(self):
        # ignore_invalid_triggers will allow to process events not defined for a state
        on = On(name='on', ignore_invalid_triggers=True)
        off = Off(name='off', ignore_invalid_triggers=False)
        self.machine = Machine(model=self, states=[on, off], transitions=self.transitions, initial='on')

machine = MyFsm()
print(machine.state)  # >>> on
machine.switchOff()
print(machine.state)  # >>> off
try:
    # this will raise a MachineException because there is no transition 'switchOff'
    # defined in state 'off'
    machine.switchOff()  # raises MachineException
    raise Exception("This exception will not be raised")
except MachineError:
    pass
print(machine.state)  # >>> off
machine.switchOn()
print(machine.state)  # >>> on
# this will NOT raise an Exception since we configured 'on'
# to ignore transitions not defined for this state
machine.switchOn()
print(machine.state)  # >>> on

Я определил некоторые классы заполнителей для On и Off, так как я предполагаю, что вы хотите использовать пользовательские классы состояний.transitions позволяет вам просто вызвать метод модели, не отслеживая текущее состояние.Ваша конфигурация будет определять, что произойдет.В зависимости от ваших потребностей вы можете повысить MachineError, если запущен метод, который не определен для вашего состояния.Рассматривая ваш пример, я бы предложил просто игнорировать недействительные триггеры, поскольку попытка дважды включить или выключить лампу не имеет большого значения.Другим решением будет «зацикливание» состояний или использование внутренних переходов, если вы хотите избежать недопустимых триггеров:

# leaves and enters state off even if its already off
# processes all on_enter/exit callbacks
{'trigger': 'switchOff', 'source':['on', 'off'], 'dest':'off'}

# an internal transition is defined by having dest set to 'None'
# processes no state callbacks
{'trigger': 'switchOff', 'source': 'off', 'dest': None}

Это сильно зависит от вашего варианта использования и системы, которой вы фактически управляетеэто конечный автомат, поведение которого должно быть предпочтительным.

0 голосов
/ 29 октября 2018

Я не уверен, что понимаю ваш вопрос, поскольку суть использования библиотеки, такой как переходы, состоит в том, чтобы избегать написания длинных цепочек if - else if. Более того, в документации никогда не упоминается, что вам следует переопределить process_event().

Так что я могу только перефразировать документацию к переходам с вашим примером, а не ifs. Я надеюсь, что это поможет вам.

from transitions import Machine


class Model:
    def __init__(self):
        self.machine = Machine(
            model=self,
            states=["on", "off"],
            transitions=[
                {
                    "trigger": "switchOff",
                    "source": "on",
                    "dest": "off",
                },
                {
                    "trigger": "switchOn",
                    "source": "off",
                    "dest": "on",
                },
            ],
            initial="on",
            ignore_invalid_triggers=True,
        )


m = Model()
assert m.state == "on"

# We are in state 'on' and there is not 'switchOn'
# transition defined: nothing happens.
#
# Without `ignore_invalid_triggers`, transitions
# would raise an exception.
m.switchOn()
assert m.state == "on"

# We still are in 'on' and 'switchOff' brings
# us to 'off.
m.switchOff()
assert m.state == "off"

Вы можете рассмотреть, например, чтение статьи в Википедии о конечных автоматах UML, поскольку переходы достаточно близки к ее реализации.

...