Skip to content

Commit d62d650

Browse files
committed
perf: replace InstanceState weakref+properties with cached __getattr__
Replace 20 boilerplate @Property declarations and weakref _ref() with __getattr__ delegation to the underlying State. Cache delegated values in __dict__ on first access so subsequent lookups are direct dict hits. Eliminates 268k __getattr__/weakref calls per benchmark cycle, yielding ~7% improvement on parallel_region_events (207µs → 193µs) and 61% reduction in ancestors() tottime.
1 parent 68f8825 commit d62d650

1 file changed

Lines changed: 21 additions & 65 deletions

File tree

statemachine/state.py

Lines changed: 21 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -350,103 +350,59 @@ def is_descendant(self, state: "State") -> bool:
350350

351351

352352
class InstanceState(State):
353-
""" """
353+
"""Per-instance proxy for a State, delegating attribute access to the underlying State.
354+
355+
Uses ``__getattr__`` for automatic delegation of instance attributes (name, value,
356+
transitions, etc.) and explicit property overrides for attributes that access private
357+
fields or have custom logic (id, initial, final, parallel, is_active).
358+
"""
354359

355360
def __init__(
356361
self,
357362
state: State,
358363
machine: "StateChart",
359364
):
360-
self._state = ref(state)
365+
self._state = state
361366
self._machine = ref(machine)
362367
self._hash = hash(state)
363368
self._init_states()
364369

365-
def _ref(self) -> State:
366-
"""Dereference the weakref, raising if the referent has been collected."""
367-
state = self._state()
368-
assert state is not None
369-
return state
370-
371-
@property
372-
def name(self):
373-
return self._ref().name
374-
375-
@property
376-
def value(self):
377-
return self._ref().value
378-
379-
@property
380-
def transitions(self):
381-
return self._ref().transitions
382-
383-
@property
384-
def enter(self):
385-
return self._ref().enter
386-
387-
@property
388-
def exit(self):
389-
return self._ref().exit
390-
391-
@property
392-
def invoke(self):
393-
return self._ref().invoke
370+
def __getattr__(self, name: str):
371+
value = getattr(self._state, name)
372+
self.__dict__[name] = value
373+
return value
394374

395375
def __eq__(self, other):
396-
return self._ref() == other
376+
return self._state == other
397377

398378
def __hash__(self):
399379
return self._hash
400380

401381
def __repr__(self):
402-
return repr(self._ref())
382+
return repr(self._state)
383+
384+
@property
385+
def id(self) -> str:
386+
return self._state._id
403387

404388
@property
405389
def initial(self):
406-
return self._ref()._initial
390+
return self._state._initial
407391

408392
@property
409393
def final(self):
410-
return self._ref()._final
394+
return self._state._final
411395

412396
@property
413-
def id(self) -> str:
414-
return (self._state() or self)._id # type: ignore[union-attr]
397+
def parallel(self):
398+
return self._state._parallel
415399

416400
@property
417401
def is_active(self):
418402
machine = self._machine()
419403
assert machine is not None
420404
return self.value in machine.configuration_values
421405

422-
@property
423-
def is_atomic(self):
424-
return self._ref().is_atomic
425-
426-
@property
427-
def parent(self):
428-
return self._ref().parent
429-
430-
@property
431-
def states(self):
432-
return self._ref().states
433-
434-
@property
435-
def history(self):
436-
return self._ref().history
437-
438-
@property
439-
def parallel(self):
440-
return self._ref().parallel
441-
442-
@property
443-
def is_compound(self):
444-
return self._ref().is_compound
445-
446-
@property
447-
def document_order(self):
448-
return self._ref().document_order
449-
450406

451407
class AnyState(State):
452408
"""A special state that works as a "ANY" placeholder.

0 commit comments

Comments
 (0)