pytransitions / transitions

A lightweight, object-oriented finite state machine implementation in Python with many extensions
MIT License
5.68k stars 530 forks source link

Problem with state names containing underscores #472

Closed ju-la closed 4 years ago

ju-la commented 4 years ago

Hi,

I'm using the library and found a problem when upgrading from 0.7.2 to 0.8.0

My code is this:

from transitions import Machine from transitions.extensions import HierarchicalMachine as Machine states = ['NOT_CONNECTED', 'CONNECTED'] machine = Machine(states=states, initial='NOT_CONNECTED')

... works for transitions 0.7.2 but not for 0.8.0

In 0.8.0 I get this: Traceback (most recent call last): File "test.py", line 6, in machine = Machine(states=states, initial='NOT_CONNECTED') File "/home/jula/.local/lib/python3.6/site-packages/transitions/extensions/nesting.py", line 342, in init _super(HierarchicalMachine, self).init(*args, *kwargs) File "/home/jula/.local/lib/python3.6/site-packages/transitions/core.py", line 565, in init self.initial = initial File "/home/jula/.local/lib/python3.6/site-packages/transitions/core.py", line 636, in initial self.add_state(state_name) File "/home/jula/.local/lib/python3.6/site-packages/transitions/core.py", line 737, in add_state self.add_states(args, kwargs) File "/home/jula/.local/lib/python3.6/site-packages/transitions/extensions/nesting.py", line 413, in add_states self.add_states(domains[1], on_enter=on_enter, on_exit=on_exit, ignore_invalid_triggers=ignore_invalid_triggers, kwargs) File "/home/jula/.local/lib/python3.6/site-packages/transitions/extensions/nesting.py", line 416, in add_states raise ValueError("State {0} cannot be added since it already exists.".format(state)) ValueError: State CONNECTED cannot be added since it already exists.

The problem is the underscore in the name.

from transitions import Machine from transitions.extensions import HierarchicalMachine as Machine states = ['NOTCONNECTED', 'CONNECTED'] machine = Machine(states=states, initial='NOTCONNECTED')

When removing it the exception is gone also for 0.8.0 and above.

Best regards

Juergen

aleneum commented 4 years ago

Hello @ju-la

From the README:

Some things that have to be considered when working with nested states: State names are concatenated with NestedState.separator. Currently the separator is set to underscore ('_') and therefore behaves similar to the basic machine. This means a substate bar from state foo will be known by foo_bar. A substate baz of bar will be referred to as foo_bar_baz and so on. When entering a substate, enter will be called for all parent states. The same is true for exiting substates. Third, nested states can overwrite transition behaviour of their parents. If a transition is not known to the current state it will be delegated to its parent.

This means that in the standard configuration, state names in HSMs MUST NOT contain underscores. For transitions it's impossible to tell whether machine.add_state('state_name') should add a state named state_name or add a substate name to the state state. In some cases this is not sufficient however. For instance if state names consists of more than one word and you want/need to use underscore to separate them instead of CamelCase. To deal with this, you can change the character used for separation quite easily. You can even use fancy unicode characters if you use Python 3.

tl;dr. You cannot use underscores in state names with the default NestedState. If you like to use underscores in your state names, you can change the state separator:

from transitions.extensions import HierarchicalMachine as Machine
from transitions.extensions.nesting import NestedState

NestedState.separator = '.'
states = ['NOT_CONNECTED', 'CONNECTED']
machine = Machine(states=states, initial='NOT_CONNECTED')
assert machine.state == 'NOT_CONNECTED'
ju-la commented 4 years ago

Thanks, aleneum, this helps. BR juergen