如何定义`transitions`状态机的触发器枚举?

问题描述

作为与 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.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,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,{'trigger': Trigger.heat,{'trigger': Trigger.cool,'dest': State.soLID}
]

def __init__(self):
    super().__init__(states=State,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的{​​{1}}来解决它:

.name

但是 transitions = [ {'trigger': Trigger.heat.name,{'trigger': Trigger.heat.name,{'trigger': Trigger.cool.name,'dest': State.soLID} ] State间的不对称让我很困扰。
难道我做错了什么?为什么枚举适用于 Trigger 而不适用于 State

解决方法

错误说明正确:

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

使其工作的更简单方法,以您希望的方式执行以下操作:

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

,

正如 vish 已经指出的,没有办法直接使用枚举作为触发器。 Transitions 将触发器名称作为方法绑定到模型,因此要求触发器是字符串。但是,Machine 和所有子类在编写时都考虑到了继承。如果你想使用枚举而不是类和类属性,你可以派生一个合适的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,**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,**kwargs):
        super().add_transition(trigger.value.lower() if hasattr(trigger,'value') else trigger,**kwargs)