transitions
transitions copied to clipboard
Very confusing in inherited HierarchicalMachine.
encountering a problem when inheriting HierarchicalMachine
.
from transitions.extensions import HierarchicalMachine as HSM
logging.basicConfig(level=logging.DEBUG)
logging.getLogger('transitions').setLevel(logging.INFO)
class P(HSM):
def __init__(self):
HSM.__init__(self, states=["A", "B"],
transitions=[["run", "A", "B"], ["run", "B", "A"]],
initial="A")
def on_enter_A(self):
print("enter A")
class T(HSM):
def __init__(self):
HSM.__init__(self, states=["Q", {"name": "P", "children": P()}],
transitions=[["go", "Q", "P"], ["go", "P", "Q"]],
initial="Q")
t = T()
t.go()
INFO:transitions.core:Finished processing state Q exit callbacks.
INFO:transitions.core:Finished processing state P enter callbacks.
Traceback (most recent call last):
File "/home/gauss/Projects/slam_project_ws/src/slamer/scripts/task_server.py", line 32, in <module>
t.go()
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/extensions/nesting.py", line 807, in trigger_event
return self._process(partial(self._trigger_event, event_data, trigger))
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/core.py", line 1211, in _process
return trigger()
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/extensions/nesting.py", line 812, in _trigger_event
res = self._trigger_event_nested(event_data, trigger, None)
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/extensions/nesting.py", line 1157, in _trigger_event_nested
tmp = event_data.event.trigger_nested(event_data)
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/extensions/nesting.py", line 140, in trigger_nested
self._process(event_data)
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/extensions/nesting.py", line 154, in _process
event_data.result = trans.execute(event_data)
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/core.py", line 277, in execute
self._change_state(event_data)
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/extensions/nesting.py", line 281, in _change_state
func()
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/extensions/nesting.py", line 214, in scoped_enter
self.enter(event_data)
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/core.py", line 129, in enter
event_data.machine.callbacks(self.on_enter, event_data)
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/core.py", line 1146, in callbacks
self.callback(func, event_data)
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/core.py", line 1163, in callback
func = self.resolve_callable(func, event_data)
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/core.py", line 1181, in resolve_callable
func = getattr(event_data.model, func)
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/core.py", line 1267, in __getattr__
state = self.get_state(target)
File "/home/gauss/.local/lib/python3.8/site-packages/transitions/extensions/nesting.py", line 624, in get_state
raise ValueError("State '%s' is not a registered state." % state)
ValueError: State '['A']' is not a registered state.
Hello @DYFeng,
thank you for the report. I see what you are trying to achieve and how transitions behaviour is confusing here. I agree that the error message is not very helpful and transitions tries to achieve the wrong thing here.
Let's talk about what you are trying to achieve though. In your setup I'd suggest to work with 'explicit' callbacks rather than defining "on_enter_
from transitions.extensions import HierarchicalMachine as HSM
class P(HSM):
def __init__(self):
HSM.__init__(self, states=[{"name": "A", "on_enter": ["notify", self.notify]}, "B"], # [1]
transitions=[["run", "A", "B"], ["run", "B", "A"]],
initial="A")
def notify(self): # [2]
print("State was entered")
class T(HSM):
def __init__(self):
HSM.__init__(self, states=["Q", {"name": "P", "children": P()}], # [3]
transitions=[["go", "Q", "P"], ["go", "P", "Q"]],
initial="Q")
def notify(self): # [4]
print("notify was overridden")
p = P()
p.to_A() # [5]
# >>> State was entered
# >>> State was entered
t = T()
t.go() # [6]
# >>> notify was overridden
# >>> State was entered
Instead of on_enter
I pass callbacks to the state definition in P
, once by reference and once by name (see [1]). If you enter state A via "p.to_A()" [5], P.notify
[2] will be called twice: Once because the underlying state object has a direct reference and once when the callback name "notify"
is resolved to a method of the model. In your case, both the machines P
and T
also act as their respective model. This is valid but not required. Consequently. T
acts as a model for T
but *P
is not a model of/for T
. What this means is illustrated in the next part.
When you pass an instance of P
to T
, transitions
will reference the state objects owned by the create instance of P
[3] which will behave similarly to our explicitly created p
[5]. When t.go()
is called the state object will process the event in the same way but there is a difference now. The callback name will be resolved on the model of machine T
(self) and transitions will have a look for notify
on t
instead of p
. If you comment [4] you will see that this resolution fails.
The Readme says:
(Nested)State instances are just referenced which means changes in one machine's collection of states and events will influence the other machine instance. Models and their state will not be shared though. Note that events and transitions are also copied by reference and will be shared by both instances if you do not use the remap keyword.
The catch is that machines do not share models. If you define callbacks by name, a model that uses a machine that makes use of nested machines must implement the callbacks. If you explicitly want to call a method of a nested machine then you should use references instead.
Concerning the error/bug: on_enter_A
of machines might be better passed by reference instead of by name when models are added to a machine. This happens during the instantiation. I will check wether this is suitable or cause side effects. Nevertheless, I hope the example above allows you to continue working on your task.