python - Pytransitions 排队分层机器总是以初始化状态结束
问题描述
我正在使用带有 HierarchicalMachine 类的 pytransitions 来创建小型嵌套机器以在更大的状态机中完成子任务。我正在使用排队转换能够从状态回调内部调用触发器。
我希望以下代码以 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
解决方案
简而言之:self
在回调中PrepareMachine
没有引用正确的模型。
长:
发生了什么,为什么?
要理解为什么会发生这种情况,必须考虑pytransitions
将状态机拆分为“规则手册”(例如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
当您不将模型参数传递给 aMachine
时,机器本身将充当模型,从而获得附加到它的所有便利和触发功能。
machine = Machine(states=states, transitions=transitions, initial="A")
assert machine.is_A()
machine.to_B()
assert machine.is_B()
因此,在您的示例中,prepare = PrepareMachine()
makeprepare
充当其自己的模型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
enterprepare_init
并因此调用PrepareMachine.entry_init
。在此方法中,您调用self.to_connecting()
它会触发prepare
toconnecting
和NOT machine
的转换。当prepare
进入时connecting
,PrepareMachine.entry_connecting
将被调用并且self
(又名prepare
)将再次转换为to_waiting
。由于PrepareMachine
和ProductionMachine
处理排队的事件,prepare
将完成to_connecting
并立即处理to_waiting
。由于 self.to_connection (aka ) 尚未返回,此时machine
仍在处理中。因此,当最终达到状态时,将返回并记录它现在已完成处理. 该模型entry_init
prepare.to_connecting
prepare
waiting
machine
start_testing
machine
没有恢复到prepare_init
但所有的处理都prepare
发生在WHILE start_testing
被处理,这导致start_testing
包装所有其他消息的日志消息。
如何达到你想要的?
我们想在正确的模型()上触发事件(to_connecting/waiting machine
)。对此有多种方法。首先,我建议定义“正确”的转换,而不是依赖自动转换。自动转换也被传递给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 和更早版本中,存在与嵌套转换的排队处理相关的错误。更新:此错误已在transitions
刚刚发布的 0.8.9 中修复。QUEUED=True
现在应该也可以了。
推荐阅读
- typescript - TypeScript 将类传递给匿名函数
- mysql - rds mysql在连接下性能变慢
- vhdl - 是否允许从另一个信号初始化一个信号?
- javascript - Webpack 4 中 css-loader 的 importLoaders 选项到底是什么?
- matlab - Matlab:文件夹路径中'../'的含义
- python - 列表的 Python Django Lambda 表达式
- vba - 跟踪已处理 VBA 的 Excel 文件
- angular - 如何在按钮单击时多次创建现有组件 - Angular
- php - 我在尝试获取 Laravel 中列的属性时遇到问题
- php - 如果通过函数调用 PHP 代码的行为不同