Skip to content

Commit d1d22c5

Browse files
committed
perf: speed up Component construction hot path
Bypass __setattr__'s freeze guard during init via vars(self).update(...); a brand-new instance can't be frozen, so the per-attribute check is pure overhead. Applied in BaseComponent.__init__, Component.__init__, and Component._create. In Component.__init__: - Lazily allocate event_triggers only when a trigger is actually seen; most components have none, so the up-front dict copy was wasted work. - Single-pass kwargs loop that handles event triggers, var props, and unknown on_* errors inline instead of double-iterating. - Defer the _validate_children imports until after the fast no-op exit.
1 parent 41e141c commit d1d22c5

1 file changed

Lines changed: 50 additions & 55 deletions

File tree

packages/reflex-base/src/reflex_base/components/component.py

Lines changed: 50 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -306,11 +306,13 @@ def __init__(
306306
"""
307307
if "children" in kwargs:
308308
kwargs["children"] = tuple(kwargs["children"])
309-
for key, value in kwargs.items():
310-
setattr(self, key, value)
309+
# Bypass ``__setattr__``'s freeze guard: a brand-new instance can't be
310+
# frozen, so the per-attribute check is pure overhead during init.
311+
d = vars(self)
312+
d.update(kwargs)
311313
for name, value in self.get_fields().items():
312314
if name not in kwargs:
313-
setattr(self, name, value.default_value())
315+
d[name] = value.default_value()
314316

315317
def __setattr__(self, key: str, value: Any) -> None:
316318
"""Block writes to frozen components, except for cache attributes.
@@ -946,76 +948,70 @@ def _post_init(self, *args, **kwargs):
946948
component_specific_triggers = self.get_event_triggers()
947949
props = self.get_props()
948950

949-
# Add any events triggers.
950-
if "event_triggers" not in kwargs:
951-
kwargs["event_triggers"] = {}
952-
kwargs["event_triggers"] = kwargs["event_triggers"].copy()
951+
# Lazily allocate the event_triggers dict only when a trigger is found.
952+
# Most components have no events; allocating up-front is pure waste.
953+
existing_triggers = kwargs.get("event_triggers")
954+
event_triggers: dict[str, Any] | None = (
955+
dict(existing_triggers) if existing_triggers else None
956+
)
957+
event_keys: list[str] = []
953958

954959
# Iterate through the kwargs and set the props.
955960
for key, value in kwargs.items():
956-
if (
957-
key.startswith("on_")
958-
and key not in component_specific_triggers
959-
and key not in props
960-
):
961-
valid_triggers = sorted(component_specific_triggers.keys())
962-
msg = (
963-
f"The {(comp_name := type(self).__name__)} does not take in an `{key}` event trigger. "
964-
f"Valid triggers for {comp_name}: {valid_triggers}. "
965-
f"If {comp_name} is a third party component make sure to add `{key}` to the component's event triggers. "
966-
f"visit https://reflex.dev/docs/wrapping-react/guide/#event-triggers for more info."
967-
)
968-
raise ValueError(msg)
969961
if key in component_specific_triggers:
970-
# Event triggers are bound to event chains.
971-
is_var = False
972-
elif key in props:
973-
# Set the field type.
974-
is_var = (
975-
field.type_origin is Var if (field := fields.get(key)) else False
962+
if event_triggers is None:
963+
event_triggers = {}
964+
event_triggers[key] = EventChain.create(
965+
value=value,
966+
args_spec=component_specific_triggers[key],
967+
key=key,
976968
)
977-
else:
969+
event_keys.append(key)
978970
continue
979971

980-
# Check whether the key is a component prop.
981-
if is_var:
972+
if key in props:
973+
field = fields.get(key)
974+
if field is None or field.type_origin is not Var:
975+
continue
982976
try:
983977
kwargs[key] = LiteralVar.create(value)
984-
985-
# Get the passed type and the var type.
986978
passed_type = kwargs[key]._var_type
987979
expected_type = typing.get_args(
988980
types.get_field_type(type(self), key)
989981
)[0]
990982
except TypeError:
991-
# If it is not a valid var, check the base types.
992983
passed_type = type(value)
993984
expected_type = types.get_field_type(type(self), key)
994985

995986
if not satisfies_type_hint(value, expected_type):
996987
value_name = value._js_expr if isinstance(value, Var) else value
997-
998988
additional_info = (
999989
" You can call `.bool()` on the value to convert it to a boolean."
1000990
if expected_type is bool and isinstance(value, Var)
1001991
else ""
1002992
)
1003-
1004993
raise TypeError(
1005994
f"Invalid var passed for prop {type(self).__name__}.{key}, expected type {expected_type}, got value {value_name} of type {passed_type}."
1006995
+ additional_info
1007996
)
1008-
# Check if the key is an event trigger.
1009-
if key in component_specific_triggers:
1010-
kwargs["event_triggers"][key] = EventChain.create(
1011-
value=value,
1012-
args_spec=component_specific_triggers[key],
1013-
key=key,
997+
continue
998+
999+
if key.startswith("on_"):
1000+
valid_triggers = sorted(component_specific_triggers.keys())
1001+
comp_name = type(self).__name__
1002+
msg = (
1003+
f"The {comp_name} does not take in an `{key}` event trigger. "
1004+
f"Valid triggers for {comp_name}: {valid_triggers}. "
1005+
f"If {comp_name} is a third party component make sure to add `{key}` to the component's event triggers. "
1006+
f"visit https://reflex.dev/docs/wrapping-react/guide/#event-triggers for more info."
10141007
)
1008+
raise ValueError(msg)
10151009

1016-
# Remove any keys that were added as events.
1017-
for key in kwargs["event_triggers"]:
1018-
kwargs.pop(key, None)
1010+
# Promote any registered event triggers; drop the raw on_* keys.
1011+
if event_triggers is not None:
1012+
kwargs["event_triggers"] = event_triggers
1013+
for key in event_keys:
1014+
kwargs.pop(key, None)
10191015

10201016
# Place data_ and aria_ attributes into custom_attrs
10211017
special_attributes = [
@@ -1086,9 +1082,9 @@ def _post_init(self, *args, **kwargs):
10861082
):
10871083
msg = f"Invalid class_name passed for prop {type(self).__name__}.class_name, expected type str, got value {class_name._js_expr} of type {class_name._var_type}."
10881084
raise TypeError(msg)
1089-
# Construct the component.
1090-
for key, value in kwargs.items():
1091-
setattr(self, key, value)
1085+
# Construct the component. Bypass ``__setattr__``'s freeze guard: the
1086+
# instance is freshly created and not yet frozen.
1087+
vars(self).update(kwargs)
10921088

10931089
@classmethod
10941090
def get_event_triggers(cls) -> dict[str, types.ArgsSpec | Sequence[types.ArgsSpec]]:
@@ -1300,8 +1296,8 @@ def _unsafe_create(
13001296
children_tuple = tuple(children)
13011297
comp = cls.__new__(cls)
13021298
super(Component, comp).__init__(id=props.get("id"), children=children_tuple)
1303-
for prop, value in props.items():
1304-
setattr(comp, prop, value)
1299+
# Bypass ``__setattr__``'s freeze guard: ``comp`` is not yet frozen.
1300+
vars(comp).update(props)
13051301
comp._freeze()
13061302
return comp
13071303

@@ -1533,19 +1529,18 @@ def _validate_component_children(self, children: list[Component]):
15331529
children: The children of the component.
15341530
15351531
"""
1536-
from reflex_components_core.base.fragment import Fragment
1537-
from reflex_components_core.core.cond import Cond
1538-
from reflex_components_core.core.foreach import Foreach
1539-
from reflex_components_core.core.match import Match
1540-
1541-
no_valid_parents_defined = all(child._valid_parents == [] for child in children)
15421532
if (
15431533
not self._invalid_children
15441534
and not self._valid_children
1545-
and no_valid_parents_defined
1535+
and all(child._valid_parents == [] for child in children)
15461536
):
15471537
return
15481538

1539+
from reflex_components_core.base.fragment import Fragment
1540+
from reflex_components_core.core.cond import Cond
1541+
from reflex_components_core.core.foreach import Foreach
1542+
from reflex_components_core.core.match import Match
1543+
15491544
comp_name = type(self).__name__
15501545
allowed_components = [
15511546
comp.__name__ for comp in (Fragment, Foreach, Cond, Match)

0 commit comments

Comments
 (0)