Subclassing any StateChart whose compound state body contains a callback raises
AttributeError: 'State' object has no attribute '_callbacks' at subclass creation time.
Reproduction
fromstatemachineimportState, StateChartclassJourney(StateChart):
classshire(State.Compound):
bag_end=State(initial=True)
green_dragon=State()
visit_pub=bag_end.to(green_dragon)
defon_enter_green_dragon(self):
...
road=State(final=True)
depart=shire.to(road)
classChild(Journey): # AttributeErrorpass
Traceback (most recent call last):
...
File "statemachine/factory.py", line 168, in _unpack_builders_callbacks
if state._callbacks:
^^^^^^^^^^^^^^^^
AttributeError: 'State' object has no attribute '_callbacks'
Cause
NestedStateFactory builds the compound as a State instance carrying _callbacks, and
StateMachineMetaclass._unpack_builders_callbacks consumes it with del state._callbacks:
def_unpack_builders_callbacks(cls):
callbacks= {}
forstateiniterate_states(cls.states):
ifstate._callbacks:
callbacks.update(state._callbacks)
delstate._callbacksadd_inherited reuses the sameState objects in the subclass, so the second metaclass run
re-reads an attribute the first run deleted. Any callback in the body triggers it, including a
plain method or an on=/cond= decorator.
Notes
Only compound bodies are affected: a flat StateChart never populates _callbacks on a state.
A compound body with no callbacks at all also subclasses fine, since _callbacks stays None
and the attribute is never deleted.
Setting state._callbacks = None instead of deleting it, or guarding with
getattr(state, "_callbacks", None), both fix the crash. The subclass does not need the
setattr again, since it inherits the attributes the base class already received.
Reproduced on the current develop.
Subclassing any
StateChartwhose compound state body contains a callback raisesAttributeError: 'State' object has no attribute '_callbacks'at subclass creation time.Reproduction
Cause
NestedStateFactorybuilds the compound as aStateinstance carrying_callbacks, andStateMachineMetaclass._unpack_builders_callbacksconsumes it withdel state._callbacks:add_inheritedreuses the sameStateobjects in the subclass, so the second metaclass runre-reads an attribute the first run deleted. Any callback in the body triggers it, including a
plain method or an
on=/cond=decorator.Notes
Only compound bodies are affected: a flat
StateChartnever populates_callbackson a state.A compound body with no callbacks at all also subclasses fine, since
_callbacksstaysNoneand the attribute is never deleted.
Setting
state._callbacks = Noneinstead of deleting it, or guarding withgetattr(state, "_callbacks", None), both fix the crash. The subclass does not need thesetattragain, since it inherits the attributes the base class already received.Reproduced on the current
develop.