@@ -915,7 +915,7 @@ def slots_setstate(self, state):
915915 """
916916 Automatically created by attrs.
917917 """
918- __bound_setattr = _obj_setattr .__get__ (self , Attribute )
918+ __bound_setattr = _obj_setattr .__get__ (self )
919919 for name , value in zip (state_attr_names , state ):
920920 __bound_setattr (name , value )
921921
@@ -2007,6 +2007,7 @@ def _make_init(
20072007 cache_hash ,
20082008 base_attr_map ,
20092009 is_exc ,
2010+ needs_cached_setattr ,
20102011 has_cls_on_setattr ,
20112012 attrs_init ,
20122013 )
@@ -2019,7 +2020,7 @@ def _make_init(
20192020 if needs_cached_setattr :
20202021 # Save the lookup overhead in __init__ if we need to circumvent
20212022 # setattr hooks.
2022- globs ["_setattr " ] = _obj_setattr
2023+ globs ["_cached_setattr_get " ] = _obj_setattr . __get__
20232024
20242025 init = _make_method (
20252026 "__attrs_init__" if attrs_init else "__init__" ,
@@ -2036,15 +2037,15 @@ def _setattr(attr_name, value_var, has_on_setattr):
20362037 """
20372038 Use the cached object.setattr to set *attr_name* to *value_var*.
20382039 """
2039- return "_setattr(self, '%s', %s)" % (attr_name , value_var )
2040+ return "_setattr('%s', %s)" % (attr_name , value_var )
20402041
20412042
20422043def _setattr_with_converter (attr_name , value_var , has_on_setattr ):
20432044 """
20442045 Use the cached object.setattr to set *attr_name* to *value_var*, but run
20452046 its converter first.
20462047 """
2047- return "_setattr(self, '%s', %s(%s))" % (
2048+ return "_setattr('%s', %s(%s))" % (
20482049 attr_name ,
20492050 _init_converter_pat % (attr_name ,),
20502051 value_var ,
@@ -2086,6 +2087,7 @@ def _attrs_to_init_script(
20862087 cache_hash ,
20872088 base_attr_map ,
20882089 is_exc ,
2090+ needs_cached_setattr ,
20892091 has_cls_on_setattr ,
20902092 attrs_init ,
20912093):
@@ -2101,6 +2103,14 @@ def _attrs_to_init_script(
21012103 if pre_init :
21022104 lines .append ("self.__attrs_pre_init__()" )
21032105
2106+ if needs_cached_setattr :
2107+ lines .append (
2108+ # Circumvent the __setattr__ descriptor to save one lookup per
2109+ # assignment.
2110+ # Note _setattr will be used again below if cache_hash is True
2111+ "_setattr = _cached_setattr_get(self)"
2112+ )
2113+
21042114 if frozen is True :
21052115 if slots is True :
21062116 fmt_setter = _setattr
@@ -2315,7 +2325,7 @@ def fmt_setter_with_converter(
23152325 if frozen :
23162326 if slots :
23172327 # if frozen and slots, then _setattr defined above
2318- init_hash_cache = "_setattr(self, '%s', %s)"
2328+ init_hash_cache = "_setattr('%s', %s)"
23192329 else :
23202330 # if frozen and not slots, then _inst_dict defined above
23212331 init_hash_cache = "_inst_dict['%s'] = %s"
@@ -2428,7 +2438,7 @@ def __init__(
24282438 )
24292439
24302440 # Cache this descriptor here to speed things up later.
2431- bound_setattr = _obj_setattr .__get__ (self , Attribute )
2441+ bound_setattr = _obj_setattr .__get__ (self )
24322442
24332443 # Despite the big red warning, people *do* instantiate `Attribute`
24342444 # themselves.
@@ -2525,7 +2535,7 @@ def __setstate__(self, state):
25252535 self ._setattrs (zip (self .__slots__ , state ))
25262536
25272537 def _setattrs (self , name_values_pairs ):
2528- bound_setattr = _obj_setattr .__get__ (self , Attribute )
2538+ bound_setattr = _obj_setattr .__get__ (self )
25292539 for name , value in name_values_pairs :
25302540 if name != "metadata" :
25312541 bound_setattr (name , value )
0 commit comments