【问题标题】:How to define enum of Trigger for `transitions` state machine?如何为“转换”状态机定义触发器枚举?
【发布时间】:2021-07-07 17:10:33
【问题描述】:

作为this answer 的不相关后续行动,它使用以下工作代码:

from transitions import Machine
from transitions import EventData
from typing import Callable
from enum import Enum, auto


class Observer:

    def state_changed(self, event_data: EventData):
        print(f"state is now '{event_data.state.name}'")


class State(Enum):
    SOLID = auto()
    LIQUID = auto()
    GAS = auto()


class SubscribableMachine(Machine):

    transitions = [
        {'trigger': 'heat', 'source': State.SOLID, 'dest': State.LIQUID},
        {'trigger': 'heat', 'source': State.LIQUID, 'dest': State.GAS},
        {'trigger': 'cool', 'source': State.GAS, 'dest': State.LIQUID},
        {'trigger': 'cool', 'source': State.LIQUID, 'dest': State.SOLID}
    ]

    def __init__(self):
        super().__init__(states=State, transitions=self.transitions,
                         initial=State.SOLID, send_event=True)

    def subscribe(self, func: Callable, state: State):
        self.get_state(state).on_enter.append(func)

    def unsubscribe(self, func: Callable, state: State):
        self.get_state(state).on_enter.remove(func)


machine = SubscribableMachine()
observer = Observer()
machine.subscribe(observer.state_changed, State.LIQUID)
machine.heat()  # >>> state is now 'LIQUID'
machine.heat()
assert machine.state == State.GAS
machine.unsubscribe(observer.state_changed, State.LIQUID)
machine.cool()  # no output
assert machine.state == State.LIQUID

我也想为Trigger 提供一个枚举,就像我为State 提供一个枚举一样。

唉,当我尝试时

class Trigger(Enum):
    heat = auto()
    cool = auto()

transitions = [
    {'trigger': Trigger.heat, 'source': State.SOLID, 'dest': State.LIQUID},
    {'trigger': Trigger.heat, 'source': State.LIQUID, 'dest': State.GAS},
    {'trigger': Trigger.cool, 'source': State.GAS, 'dest': State.LIQUID},
    {'trigger': Trigger.cool, 'source': State.LIQUID, 'dest': State.SOLID}
]

def __init__(self):
    super().__init__(states=State, transitions=self.transitions,
                     initial=State.SOLID, send_event=True)

我明白了

Traceback (most recent call last):
  File "C:/code/EPMD/Kodex/Algorithms/src/python/epmd/ablation_points/queries/dfgfdsg.py", line 42, in <module>
    machine = SubscribableMachine()
  File "C:/code/EPMD/Kodex/Algorithms/src/python/epmd/ablation_points/queries/dfgfdsg.py", line 33, in __init__
    initial=State.SOLID, send_event=True)
  File "C:\Code\EPMD\Kodex\EPD_Prerequisite\python_3.7.6\Lib\site-packages\transitions\core.py", line 589, in __init__
    self.add_model(model)
  File "C:\Code\EPMD\Kodex\EPD_Prerequisite\python_3.7.6\Lib\site-packages\transitions\core.py", line 607, in add_model
    self._add_trigger_to_model(trigger, mod)
  File "C:\Code\EPMD\Kodex\EPD_Prerequisite\python_3.7.6\Lib\site-packages\transitions\core.py", line 813, in _add_trigger_to_model
    self._checked_assignment(model, trigger, partial(self.events[trigger].trigger, model))
  File "C:\Code\EPMD\Kodex\EPD_Prerequisite\python_3.7.6\Lib\site-packages\transitions\core.py", line 807, in _checked_assignment
    if hasattr(model, name):
TypeError: hasattr(): attribute name must be string

我可以用Enum.name解决它:

    transitions = [
        {'trigger': Trigger.heat.name, 'source': State.SOLID, 'dest': State.LIQUID},
        {'trigger': Trigger.heat.name, 'source': State.LIQUID, 'dest': State.GAS},
        {'trigger': Trigger.cool.name, 'source': State.GAS, 'dest': State.LIQUID},
        {'trigger': Trigger.cool.name, 'source': State.LIQUID, 'dest': State.SOLID}
    ]

但是StateTrigger 之间的不对称让我很困扰。
难道我做错了什么?为什么枚举适用于 State 而不是 Trigger

【问题讨论】:

    标签: python state-machine pytransitions


    【解决方案1】:

    正如vish 已经指出的那样,没有办法直接使用枚举作为触发器。 Transitions 将触发器名称作为方法绑定到模型,因此要求触发器是字符串。但是,Machine 和所有子类在编写时都考虑到了继承。如果你想使用 Enums 代替类和类属性,你只需派生一个合适的EnumTransitionMachine 并得到一个统一的接口:

    from transitions import Machine
    from enum import Enum, auto
    
    
    class State(Enum):
        A = auto()
        B = auto()
        C = auto()
    
    
    class Transitions(Enum):
        GO = auto()
        PROCEED = auto()
    
    
    class EnumTransitionMachine(Machine):
    
        def add_transition(self, trigger, *args, **kwargs):
            super().add_transition(trigger.name.lower() if hasattr(trigger, 'name') else trigger, *args, **kwargs)
    
    
    transitions = [[Transitions.GO, State.A, State.B], [Transitions.PROCEED, State.B, State.C]]
    m = EnumTransitionMachine(states=State, transitions=transitions, initial=State.A)
    m.go()
    assert m.state == State.B
    m.proceed()
    assert m.is_C()
    

    仅供参考:也可以将Enums 与字符串值一起使用:

    class State(Enum):
        A = "A"
        B = "B"
        C = "C"
    
    
    class Transitions(Enum):
        GO = "GO"
        PROCEED = "PROCEED"
    
    # you could use the enum's value then instead:
        def add_transition(self, trigger, *args, **kwargs):
            super().add_transition(trigger.value.lower() if hasattr(trigger, 'value') else trigger, *args, **kwargs)
    

    【讨论】:

      【解决方案2】:

      错误说得对:

      TypeError: hasattr(): attribute name must be string。如果您浏览枚举文档 (https://docs.python.org/3/library/enum.html),您将看到每个函数返回的内容。您可以覆盖它们,看看是否有效。除非Machine 中的代码专门检查Enum 并将其转换为字符串,否则它将无法正常工作。

      让它工作的更简单的方法是,以某种你想要的方式来代替:

      class Trigger:
          HEAT = 'HEAT'
          COOL = 'COOL'
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-11-02
        • 2018-02-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-07
        • 1970-01-01
        相关资源
        最近更新 更多