Pytransitions 排队的分层机器总是以初始状态结束
Pytransitions queued hierarchical machine ends always in init state
我将 pytransitions 与 HierarchicalMachine class 结合使用,以便能够创建小型嵌套机器以在更大的状态机中完成子任务。我正在使用排队的转换来从状态回调内部调用触发器。
我希望以下代码以 prepare_waiting 状态结束,但它实际上返回到 prepare_init 状态。
你知道为什么会这样吗?
代码:
from transitions.extensions.factory import HierarchicalMachine
import logging as log
QUEUED = True
class PrepareMachine(HierarchicalMachine):
def __init__(self):
states = [
{"name": "init", "on_enter": self.entry_init},
{"name": "connecting", "on_enter": self.entry_connecting},
{"name": "waiting", "on_enter": self.entry_waiting},
]
super().__init__(states=states, initial="init", queued=QUEUED)
def entry_init(self):
print("common entry point ...")
self.to_connecting()
def entry_connecting(self):
print("connecting multiple indtruments ...")
self.to_waiting()
def entry_waiting(self):
print("wait for response ...")
class ProductionMachine(HierarchicalMachine):
def __init__(self):
prepare = PrepareMachine()
states = ["init", {"name": "prepare", "children": prepare}]
super().__init__(states=states, initial="init", queued=QUEUED)
self.add_transition("start_testing", "init", "prepare")
log.basicConfig(level=log.INFO)
machine = ProductionMachine()
machine.start_testing()
print(machine.state)
输出:
INFO:transitions.core:Finished processing state init exit callbacks.
INFO:transitions.core:Finished processing state prepare enter callbacks.
common entry point ...
INFO:transitions.core:Finished processing state init exit callbacks.
connecting multiple indtruments ...
INFO:transitions.core:Executed callback '<bound method PrepareMachine.entry_connecting of <__main__.PrepareMachine object at 0xb6588bd0>>'
INFO:transitions.core:Finished processing state connecting enter callbacks.
INFO:transitions.core:Finished processing state connecting exit callbacks.
wait for response ...
INFO:transitions.core:Executed callback '<bound method PrepareMachine.entry_waiting of <__main__.PrepareMachine object at 0xb6588bd0>>'
INFO:transitions.core:Finished processing state waiting enter callbacks.
INFO:transitions.core:Executed callback '<bound method PrepareMachine.entry_init of <__main__.PrepareMachine object at 0xb6588bd0>>'
INFO:transitions.core:Finished processing state init enter callbacks.
prepare_init
不足:PrepareMachine
的回调中的 self
没有引用正确的模型。
长:
发生了什么,为什么?
要理解为什么会发生这种情况,必须考虑 pytransitions
的概念,它将状态机拆分为包含所有状态、事件和转换的 'rule book'(例如 Machine
)定义和通常称为模型的有状态对象。所有便利功能,如触发功能(以转换名称命名的方法)或自动转换(如 to_<state>
)都附加到模型。
machine = Machine(model=model, states=states, transitions=transitions, initial="initial")
assert model.is_initial() #
model.to_work() # auto transition
assert model.is_work()
machine.to_initial() <-- will raise an exception
当您不将模型参数传递给 Machine
时,机器本身将充当模型,从而获得附加到它的所有便利和触发功能。
machine = Machine(states=states, transitions=transitions, initial="A")
assert machine.is_A()
machine.to_B()
assert machine.is_B()
因此,在您的示例中,prepare = PrepareMachine()
使 prepare
充当其自己的模型,而 machine = ProductionMachine()
使 machine
成为 ProductionMachine
的模型。这就是为什么您可以调用 prepare.to_connecting()
的原因,因为 prepapre
也是一个模型。但是,不是您想要的模型 machine
。所以如果我们稍微改变一下你的例子,事情可能会变得更清楚一点:
class ProductionMachine(HierarchicalMachine):
def __init__(self):
self.prepare = PrepareMachine()
states = ["init", {"name": "prepare", "children": self.prepare}]
# [...]
machine = ProductionMachine()
machine.start_testing()
print(machine.state) # >>> prepare_init
print(machine.prepare.state) # >>> waiting
使用 machine.start_testing()
,您让 machine
输入 prepare_init
,然后调用 PrepareMachine.entry_init
。在此方法中,您调用 self.to_connecting()
,它会触发从 prepare
到 connecting
和 NOT machine
的转换。当 prepare
进入 connecting
时,将调用 PrepareMachine.entry_connecting
并且 self
(又名 prepare
)将再次转换为 to_waiting
。由于 PrepareMachine
和 ProductionMachine
处理排队的事件,prepare
将完成 to_connecting
并立即处理 to_waiting
。此时 machine
仍在处理 entry_init
,因为 self.to_connection(又名 prepare.to_connecting
)尚未 return。因此,当 prepare
最终达到 waiting
状态时,machine
将 return 并记录它现在已完成 start_testing
的处理转换回调。模型 machine
没有恢复到 prepare_init
但 prepare
的所有处理发生 WHILE start_testing
被处理,这导致日志start_testing
的消息包装所有其他消息。
如何实现你想要的?
我们想在正确的模型 (machine
) 上触发事件 (to_connecting/waiting)。有多种方法可以解决这个问题。首先,我建议定义 'proper' 转换而不是依赖自动转换。自动转换也会传递给 machine
(因此 machine.to_connecting
会起作用),当您有多个同名子状态时,事情可能会变得混乱。
选项 A:从 event_data 获取正确的模型。
当您将 send_event=True
传递给 Machine
构造函数时,每个回调都可以(并且必须)接受一个 EventData
对象,该对象包含有关当前处理的转换的所有信息。这包括模型。
transitions = [
["connect", "init", "connecting"],
["connected", "connecting", "waiting"]
]
states = [
{"name": "init", "on_enter": self.entry_init},
{"name": "connecting", "on_enter": self.entry_connecting},
{"name": "waiting", "on_enter": self.entry_waiting},
]
# ...
def entry_init(self, event_data):
print("common entry point ...")
event_data.model.connect()
# we could use event_data.model.to_connecting() as well
# but I'd recommend defining transitions with 'proper' names
# focused on events
def entry_connecting(self, event_data):
print("connecting multiple instruments ...")
event_data.model.connected()
def entry_waiting(self, event_data):
print("wait for response ...")
# ...
super().__init__(states=states, transitions=transitions, initial="init", queued=QUEUED, send_event=True)
选项 B:使用回调名称而不是引用并将它们直接传递给 on_enter
。
当回调参数是名称时,transitions
将在当前处理的模型上解析回调。参数 on_enter
允许传递多个回调,还可以混合引用和字符串。所以你的代码看起来像这样。
from transitions.extensions.factory import HierarchicalMachine
import logging as log
QUEUED = False
class PrepareMachine(HierarchicalMachine):
def __init__(self):
transitions = [
["connect", "init", "connecting"],
["connected", "connecting", "waiting"]
]
states = [
{"name": "init", "on_enter": [self.entry_init, "connect"]},
{"name": "connecting", "on_enter": [self.entry_connecting, "connected"]},
{"name": "waiting", "on_enter": self.entry_waiting},
]
super().__init__(states=states, transitions=transitions, initial="init", queued=QUEUED)
def entry_init(self):
print("common entry point ...")
def entry_connecting(self):
print("connecting multiple indtruments ...")
def entry_waiting(self):
print("wait for response ...")
class ProductionMachine(HierarchicalMachine):
def __init__(self):
self.prepare = PrepareMachine()
states = ["init", {"name": "prepare", "children": self.prepare}]
super().__init__(states=states, initial="init", queued=QUEUED)
self.add_transition("start_testing", "init", "prepare")
log.basicConfig(level=log.INFO)
machine = ProductionMachine()
machine.start_testing()
assert machine.is_prepare_waiting()
请注意,我不得不切换 QUEUED=False
,因为在 transitions
0.8.8 和更早版本中,有一个 bug 与嵌套转换的排队处理相关。 更新: 这个错误已经在刚刚发布的 transitions
0.8.9 中修复。 QUEUED=True
现在应该也可以工作了。
我将 pytransitions 与 HierarchicalMachine class 结合使用,以便能够创建小型嵌套机器以在更大的状态机中完成子任务。我正在使用排队的转换来从状态回调内部调用触发器。
我希望以下代码以 prepare_waiting 状态结束,但它实际上返回到 prepare_init 状态。
你知道为什么会这样吗?
代码:
from transitions.extensions.factory import HierarchicalMachine
import logging as log
QUEUED = True
class PrepareMachine(HierarchicalMachine):
def __init__(self):
states = [
{"name": "init", "on_enter": self.entry_init},
{"name": "connecting", "on_enter": self.entry_connecting},
{"name": "waiting", "on_enter": self.entry_waiting},
]
super().__init__(states=states, initial="init", queued=QUEUED)
def entry_init(self):
print("common entry point ...")
self.to_connecting()
def entry_connecting(self):
print("connecting multiple indtruments ...")
self.to_waiting()
def entry_waiting(self):
print("wait for response ...")
class ProductionMachine(HierarchicalMachine):
def __init__(self):
prepare = PrepareMachine()
states = ["init", {"name": "prepare", "children": prepare}]
super().__init__(states=states, initial="init", queued=QUEUED)
self.add_transition("start_testing", "init", "prepare")
log.basicConfig(level=log.INFO)
machine = ProductionMachine()
machine.start_testing()
print(machine.state)
输出:
INFO:transitions.core:Finished processing state init exit callbacks.
INFO:transitions.core:Finished processing state prepare enter callbacks.
common entry point ...
INFO:transitions.core:Finished processing state init exit callbacks.
connecting multiple indtruments ...
INFO:transitions.core:Executed callback '<bound method PrepareMachine.entry_connecting of <__main__.PrepareMachine object at 0xb6588bd0>>'
INFO:transitions.core:Finished processing state connecting enter callbacks.
INFO:transitions.core:Finished processing state connecting exit callbacks.
wait for response ...
INFO:transitions.core:Executed callback '<bound method PrepareMachine.entry_waiting of <__main__.PrepareMachine object at 0xb6588bd0>>'
INFO:transitions.core:Finished processing state waiting enter callbacks.
INFO:transitions.core:Executed callback '<bound method PrepareMachine.entry_init of <__main__.PrepareMachine object at 0xb6588bd0>>'
INFO:transitions.core:Finished processing state init enter callbacks.
prepare_init
不足:PrepareMachine
的回调中的 self
没有引用正确的模型。
长:
发生了什么,为什么?
要理解为什么会发生这种情况,必须考虑 pytransitions
的概念,它将状态机拆分为包含所有状态、事件和转换的 'rule book'(例如 Machine
)定义和通常称为模型的有状态对象。所有便利功能,如触发功能(以转换名称命名的方法)或自动转换(如 to_<state>
)都附加到模型。
machine = Machine(model=model, states=states, transitions=transitions, initial="initial")
assert model.is_initial() #
model.to_work() # auto transition
assert model.is_work()
machine.to_initial() <-- will raise an exception
当您不将模型参数传递给 Machine
时,机器本身将充当模型,从而获得附加到它的所有便利和触发功能。
machine = Machine(states=states, transitions=transitions, initial="A")
assert machine.is_A()
machine.to_B()
assert machine.is_B()
因此,在您的示例中,prepare = PrepareMachine()
使 prepare
充当其自己的模型,而 machine = ProductionMachine()
使 machine
成为 ProductionMachine
的模型。这就是为什么您可以调用 prepare.to_connecting()
的原因,因为 prepapre
也是一个模型。但是,不是您想要的模型 machine
。所以如果我们稍微改变一下你的例子,事情可能会变得更清楚一点:
class ProductionMachine(HierarchicalMachine):
def __init__(self):
self.prepare = PrepareMachine()
states = ["init", {"name": "prepare", "children": self.prepare}]
# [...]
machine = ProductionMachine()
machine.start_testing()
print(machine.state) # >>> prepare_init
print(machine.prepare.state) # >>> waiting
使用 machine.start_testing()
,您让 machine
输入 prepare_init
,然后调用 PrepareMachine.entry_init
。在此方法中,您调用 self.to_connecting()
,它会触发从 prepare
到 connecting
和 NOT machine
的转换。当 prepare
进入 connecting
时,将调用 PrepareMachine.entry_connecting
并且 self
(又名 prepare
)将再次转换为 to_waiting
。由于 PrepareMachine
和 ProductionMachine
处理排队的事件,prepare
将完成 to_connecting
并立即处理 to_waiting
。此时 machine
仍在处理 entry_init
,因为 self.to_connection(又名 prepare.to_connecting
)尚未 return。因此,当 prepare
最终达到 waiting
状态时,machine
将 return 并记录它现在已完成 start_testing
的处理转换回调。模型 machine
没有恢复到 prepare_init
但 prepare
的所有处理发生 WHILE start_testing
被处理,这导致日志start_testing
的消息包装所有其他消息。
如何实现你想要的?
我们想在正确的模型 (machine
) 上触发事件 (to_connecting/waiting)。有多种方法可以解决这个问题。首先,我建议定义 'proper' 转换而不是依赖自动转换。自动转换也会传递给 machine
(因此 machine.to_connecting
会起作用),当您有多个同名子状态时,事情可能会变得混乱。
选项 A:从 event_data 获取正确的模型。
当您将 send_event=True
传递给 Machine
构造函数时,每个回调都可以(并且必须)接受一个 EventData
对象,该对象包含有关当前处理的转换的所有信息。这包括模型。
transitions = [
["connect", "init", "connecting"],
["connected", "connecting", "waiting"]
]
states = [
{"name": "init", "on_enter": self.entry_init},
{"name": "connecting", "on_enter": self.entry_connecting},
{"name": "waiting", "on_enter": self.entry_waiting},
]
# ...
def entry_init(self, event_data):
print("common entry point ...")
event_data.model.connect()
# we could use event_data.model.to_connecting() as well
# but I'd recommend defining transitions with 'proper' names
# focused on events
def entry_connecting(self, event_data):
print("connecting multiple instruments ...")
event_data.model.connected()
def entry_waiting(self, event_data):
print("wait for response ...")
# ...
super().__init__(states=states, transitions=transitions, initial="init", queued=QUEUED, send_event=True)
选项 B:使用回调名称而不是引用并将它们直接传递给 on_enter
。
当回调参数是名称时,transitions
将在当前处理的模型上解析回调。参数 on_enter
允许传递多个回调,还可以混合引用和字符串。所以你的代码看起来像这样。
from transitions.extensions.factory import HierarchicalMachine
import logging as log
QUEUED = False
class PrepareMachine(HierarchicalMachine):
def __init__(self):
transitions = [
["connect", "init", "connecting"],
["connected", "connecting", "waiting"]
]
states = [
{"name": "init", "on_enter": [self.entry_init, "connect"]},
{"name": "connecting", "on_enter": [self.entry_connecting, "connected"]},
{"name": "waiting", "on_enter": self.entry_waiting},
]
super().__init__(states=states, transitions=transitions, initial="init", queued=QUEUED)
def entry_init(self):
print("common entry point ...")
def entry_connecting(self):
print("connecting multiple indtruments ...")
def entry_waiting(self):
print("wait for response ...")
class ProductionMachine(HierarchicalMachine):
def __init__(self):
self.prepare = PrepareMachine()
states = ["init", {"name": "prepare", "children": self.prepare}]
super().__init__(states=states, initial="init", queued=QUEUED)
self.add_transition("start_testing", "init", "prepare")
log.basicConfig(level=log.INFO)
machine = ProductionMachine()
machine.start_testing()
assert machine.is_prepare_waiting()
请注意,我不得不切换 QUEUED=False
,因为在 transitions
0.8.8 和更早版本中,有一个 bug 与嵌套转换的排队处理相关。 更新: 这个错误已经在刚刚发布的 transitions
0.8.9 中修复。 QUEUED=True
现在应该也可以工作了。