Skip to content

Commit ae8c6cd

Browse files
perf: avoid dynamic default code path for static immutable defaults
1 parent a3862e0 commit ae8c6cd

File tree

1 file changed

+41
-1
lines changed

1 file changed

+41
-1
lines changed

traitlets/traitlets.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,7 @@ def setup_class(cls, classdict):
990990
# also looking at base classes
991991
cls._all_trait_default_generators = {}
992992
cls._traits = {}
993+
cls._static_immutable_initial_values = {}
993994

994995
super().setup_class(classdict)
995996

@@ -1014,6 +1015,42 @@ def setup_class(cls, classdict):
10141015
cls._all_trait_default_generators[name] = c._trait_default_generators[name] # type: ignore[attr-defined]
10151016
break
10161017
else:
1018+
# We don't have a dynamic default generator using @default etc.
1019+
# Now if the default value is not dynamic and immutable (string, number)
1020+
# and does not require any validation, we keep them in a dict
1021+
# of initial values to speed up instance creation.
1022+
# This is a very specific optimization, but a very common scenario in
1023+
# for instance ipywidgets.
1024+
none_ok = trait.default_value is None and trait.allow_none
1025+
if (
1026+
type(trait) in [CInt, Int]
1027+
and trait.min is None
1028+
and trait.max is None
1029+
and (isinstance(trait.default_value, int) or none_ok)
1030+
):
1031+
cls._static_immutable_initial_values[name] = trait.default_value
1032+
elif (
1033+
type(trait) in [CFloat, Float]
1034+
and trait.min is None
1035+
and trait.max is None
1036+
and (isinstance(trait.default_value, float) or none_ok)
1037+
):
1038+
cls._static_immutable_initial_values[name] = trait.default_value
1039+
elif type(trait) in [CBool, Bool] and (
1040+
isinstance(trait.default_value, bool) or none_ok
1041+
):
1042+
cls._static_immutable_initial_values[name] = trait.default_value
1043+
elif type(trait) in [CUnicode, Unicode] and (
1044+
isinstance(trait.default_value, str) or none_ok
1045+
):
1046+
cls._static_immutable_initial_values[name] = trait.default_value
1047+
elif type(trait) == Any and (
1048+
isinstance(trait.default_value, (str, int, float, bool)) or none_ok
1049+
):
1050+
cls._static_immutable_initial_values[name] = trait.default_value
1051+
1052+
# we always add it, because a class may change when we call add_trait
1053+
# and then the instance may not have all the _static_immutable_initial_values
10171054
cls._all_trait_default_generators[name] = trait.default
10181055

10191056

@@ -1235,6 +1272,7 @@ def setup_instance(*args, **kwargs):
12351272

12361273
class HasTraits(HasDescriptors, metaclass=MetaHasTraits):
12371274
_trait_values: t.Dict[str, t.Any]
1275+
_static_immutable_initial_values: t.Dict[str, t.Any]
12381276
_trait_notifiers: t.Dict[str, t.Any]
12391277
_trait_validators: t.Dict[str, t.Any]
12401278
_cross_validation_lock: bool
@@ -1246,7 +1284,9 @@ def setup_instance(*args, **kwargs):
12461284
self = args[0]
12471285
args = args[1:]
12481286

1249-
self._trait_values = {}
1287+
self._trait_values = {
1288+
k: v for k, v in self._static_immutable_initial_values.items() if k not in kwargs
1289+
}
12501290
self._trait_notifiers = {}
12511291
self._trait_validators = {}
12521292
self._cross_validation_lock = False

0 commit comments

Comments
 (0)