重命名已应用于机器的状态和转换

Renaming States and Transitions already applied to a Machine

我目前正在为 Python 使用 tyarkoni/transitions 动态创建状态机。但是,我已经到了想要重命名状态及其转换的地步。我想知道是否有一种方法可以在不删除并重新添加不同名称的状态的情况下执行此操作。

更具体地说,我正在创建状态和转换,同时通过具有一组子系统的未知系统和 names/IDs 的实际 names/IDs 进行 BF 搜索33=] 有时可以在搜索的后期 "discovered" 找到状态并将其添加到状态机(然后保存在临时 name/id 下)。然后我想在程序的其他部分重命名它们以便于处理等。

有没有简单的方法来做到这一点?

有关我认为您将如何重命名状态的示例

from transitions import Machine
s = ['A','B','C'] # States
t = [['ab','A','B'],['bc','B','C'],['ca','C','A']] # Transitions
m = Machine(states=s,transitions=t,initial='A')
m.state # Current state ('A')
m.get_state('A') # Gets state 'A' (<State('A')@140486642630440>)
m.get_state('A').name # Gets state name 'A' ('A')
m.get_state('A').name = 'D' # Renames state name 'A' to 'D'
m.get_state('A') # Gets state 'A' (<State('D')@140486642630440>)
m.get_state('A').name # Gets state name 'A' ('D')
# The following generates an error, since the state isn't renamed in the machine
m.get_state('D') # (ValueError: State 'D' is not a registered state.)

如上结果在Python

中执行
>>> from transitions import Machine
>>> s = ['A','B','C'] # States
>>> t = [['ab','A','B'],['bc','B','C'],['ca','C','A']] # Transitions
>>> m = Machine(states=s,transitions=t,initial='A')
>>> m.state # Current state ('A')
'A'
>>> m.get_state('A') # Gets state 'A' (<State('A')@140486642630440>)
<State('A')@140329230661784>
>>> m.get_state('A').name # Gets state name 'A' ('A')
'A'
>>> m.get_state('A').name = 'D' # Renames state name 'A' to 'D'
>>> m.get_state('A') # Gets state 'A' (<State('D')@140486642630440>)
<State('D')@140329230661784>
>>> m.get_state('A').name # Gets state name 'A' ('D')
'D'
>>> # The following generates an error, since the state isn't renamed in the machine
>>> m.get_state('D') # (ValueError: State 'D' is not a registered state.)
ValueError: State 'D' is not a registered state.

这不起作用,因为我只重命名了名称字符串,而不是实际状态。 我想要的结果是将所有对状态 ('A') 的引用更改为 ('D'),因此例如机器 m 的转换将是 [['ab','D','B'],['bc','B','C'],['ca','C','D']]

这可能吗,还是我必须坚持 adding/removing 状态及其所有转换参考?如果是这样,有什么可行的方法来做到这一点?

简答:没那么容易。最稳定的方法是动态调整的外部名称映射。

names = {"A": "stateA", "B": "stateB"}
names["A"] = "stateC"

这也可以在 State 的子 class 中完成,其中 State.name 保留用于机器逻辑,State.system_name 可以在不造成伤害的情况下进行更改。

如果您 want/need 实际替换州名。继续阅读。

更长的答案: Transitions 在 OrderedDict 中管理状态,其中键代表状态名称。此外,转换被收集在列表字典中的事件中,同样以源状态名称作为键。这些事件作为一种方法绑定到模型,该方法的名称类似于转换的触发器。转换本身包含属性 sourcedest,它们包含它们的开始和目的地的 .. wait it ... 状态名称。 如果启用了自动转换,我们还有 to_<state.name> 分配给模型的功能。还有更多与模型相关的状态名称相关纠缠,包括 on_enter/exit_<state.name>。这意味着需要替换字符串的地方很多。

Where there's a will, there's a way. -- some Python coder somewhere

您可以深入挖掘并重命名所有与状态相关的内容,但这会有一些注意事项。最明显的一个:如果您的模型 class 定义了一个 on_enter_A,您不能只重命名实例中的方法。在状态实例初始化期间,它已被添加到状态中。您可以从 State.on_enter/exit 属性中过滤方法或让它们分配。这也意味着,如果您将状态从 "A" 重命名为 "B",即使模型中存在 on_enter_B 方法,它也不会重新分配回调。

from transitions import Machine
from collections import OrderedDict


def rename_state(machine, old, new):
    # rename transitions
    for trigger, event in machine.events.items():
        if old in event.transitions:
            trs = event.transitions.pop(old)
            for tr in trs:
                tr.source = new
            event.transitions[new] = trs
        for trs in event.transitions.values():
            for tr in trs:
                if tr.dest == old:
                    tr.dest = new

    if "to_{0}".format(old) in machine.events:
        ev = machine.events.pop("to_{0}".format(old))
        machine.events["to_{0}".format(new)] = ev
    # rename model properties
    for mo in machine.models:
        func = getattr(mo, 'to_' + old, False)
        if func:
            setattr(mo, 'to_' + new, func)
            delattr(mo, 'to_' + old)
        if mo.state == old:
            mo.state = new
    # rename state and reconstruct ordered dict for states
    machine.states[old].name = new
    machine.states = OrderedDict((new if k == old else k, v) for k, v in machine.states.viewitems())


class Model:

    def on_exit_A(self):
        print("Exited A!")

    def on_exit_B(self):
        print("Exited B")

model = Model()
m = Machine(model, initial='A')
m.add_state("A")
m.add_state("C")
m.add_transition('go', 'A', 'C')
rename_state(m, "A", "B")
print model.state  # >>> B
model.go()  # >>> Exited A!
print model.state  # >>> C
print m.get_state('B')

有(更好的)替代品吗?

您可以从机器中删除转换,但如果您计划删除并重新添加状态和转换,则必须以困难的方式过滤掉状态。

您还可以有一个转换和状态列表,并将其保存在某处。如果您需要重命名状态,您可以更改列表并创建一个新机器:

from transitions import Machine

states = ["A", "B"]
transitions = [['ab', 'A', 'B'], ['ba', 'B', 'A']]


class Model():
    pass

model = Model()

m = Machine(model, states=states, transitions=transitions, initial='A')
model.ab()
print model.state  # >>> B

# rename states and transitions
states = [s if s != "A" else "C" for s in states]
transitions = [[t, s if s != "A" else "C", d if d != "A" else "C"] for t, s, d in transitions]

m = Machine(model, states=states, transitions=transitions, initial=model.state)
model.ba()
print model.state  # >>> C

然而,这也存在风险。如前所述,机器将某些功能分配给模型。如果您无法重新初始化您的模型,您至少会为该模型分配一些损坏的自动转换。尝试在第二个示例中调用 model.to_A() 以了解我的意思。