@@ -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
12361273class 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