首页 > 解决方案 > 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

标签: pythonpytransitions

解决方案


简而言之: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()并使. 这就是为什么你可以打电话的原因,因为它也是一个模型。但是,不是您想要的模型。因此,如果我们稍微更改您的示例,事情可能会变得更加清晰:machineProductionMachineprepare.to_connecting()prepapremachine

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()你让machineenterprepare_init并因此调用PrepareMachine.entry_init。在此方法中,您调用self.to_connecting()它会触发preparetoconnectingNOT machine的转换。当prepare进入时connectingPrepareMachine.entry_connecting将被调用并且self(又名prepare)将再次转换为to_waiting。由于PrepareMachineProductionMachine处理排队的事件,prepare将完成to_connecting并立即处理to_waiting。由于 self.to_connection (aka ) 尚未返回,此时machine仍在处理中。因此,当最终达到状态时,将返回并记录它现在已完成处理. 该模型entry_initprepare.to_connectingpreparewaitingmachinestart_testingmachine没有恢复到prepare_init但所有的处理都prepare发生在WHILE start_testing被处理,这导致start_testing包装所有其他消息的日志消息。

如何达到你想要的?

我们想在正确的模型()上触发事件(to_connecting/waiting machine)。对此有多种方法。首先,我建议定义“正确”的转换,而不是依赖自动转换。自动转换也被传递给machine(所以machine.to_connecting会起作用),当您有多个具有相同名称的子状态时,事情可能会变得混乱。

选项 A:从 event_data 中获取正确的模型。

当您传递send_event=TrueMachine构造函数时,每个回调都可以(并且必须)接受一个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因为在transitions0.8.8 和更早版本中,存在与嵌套转换的排队处理相关的错误。更新:此错误已在transitions刚刚发布的 0.8.9 中修复。QUEUED=True现在应该也可以了。


推荐阅读