Skip to content

Commit 6151683

Browse files
authored
Eliminate most str.format() and %-formatting (#995)
* Eliminate most str.format() and %-formatting * Add newsfragment
1 parent a819155 commit 6151683

6 files changed

Lines changed: 53 additions & 73 deletions

File tree

changelog.d/995.change.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Class-creation performance improvements by switching performance-sensitive templating operations to f-strings.
2+
3+
You can expect an improvement of about 5% even for very simple classes.

docs/why.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ is roughly
218218
... self.b = b
219219
...
220220
... def __repr__(self):
221-
... return "ArtisanalClass(a={}, b={})".format(self.a, self.b)
221+
... return f"ArtisanalClass(a={self.a}, b={self.b})"
222222
...
223223
... def __eq__(self, other):
224224
... if other.__class__ is self.__class__:

src/attr/_cmp.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,9 @@ def method(self, other):
130130

131131
return result
132132

133-
method.__name__ = "__%s__" % (name,)
134-
method.__doc__ = "Return a %s b. Computed by attrs." % (
135-
_operation_names[name],
133+
method.__name__ = f"__{name}__"
134+
method.__doc__ = (
135+
f"Return a {_operation_names[name]} b. Computed by attrs."
136136
)
137137

138138
return method

src/attr/_funcs.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,9 +331,7 @@ def assoc(inst, **changes):
331331
a = getattr(attrs, k, NOTHING)
332332
if a is NOTHING:
333333
raise AttrsAttributeNotFoundError(
334-
"{k} is not an attrs attribute on {cl}.".format(
335-
k=k, cl=new.__class__
336-
)
334+
f"{k} is not an attrs attribute on {new.__class__}."
337335
)
338336
_obj_setattr(new, k, v)
339337
return new

src/attr/_make.py

Lines changed: 44 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@
2323
# This is used at least twice, so cache it here.
2424
_obj_setattr = object.__setattr__
2525
_init_converter_pat = "__attr_converter_%s"
26-
_init_factory_pat = "__attr_factory_{}"
27-
_tuple_property_pat = (
28-
" {attr_name} = _attrs_property(_attrs_itemgetter({index}))"
29-
)
26+
_init_factory_pat = "__attr_factory_%s"
3027
_classvar_prefixes = (
3128
"typing.ClassVar",
3229
"t.ClassVar",
@@ -342,7 +339,7 @@ class MyClassAttributes(tuple):
342339
if attr_names:
343340
for i, attr_name in enumerate(attr_names):
344341
attr_class_template.append(
345-
_tuple_property_pat.format(index=i, attr_name=attr_name)
342+
f" {attr_name} = _attrs_property(_attrs_itemgetter({i}))"
346343
)
347344
else:
348345
attr_class_template.append(" pass")
@@ -559,7 +556,7 @@ def _transform_attrs(
559556
if had_default is True and a.default is NOTHING:
560557
raise ValueError(
561558
"No mandatory attributes allowed after an attribute with a "
562-
"default value or factory. Attribute in question: %r" % (a,)
559+
f"default value or factory. Attribute in question: {a!r}"
563560
)
564561

565562
if had_default is False and a.default is not NOTHING:
@@ -1062,8 +1059,9 @@ def _add_method_dunders(self, method):
10621059
pass
10631060

10641061
try:
1065-
method.__doc__ = "Method generated by attrs for class %s." % (
1066-
self._cls.__qualname__,
1062+
method.__doc__ = (
1063+
"Method generated by attrs for class "
1064+
f"{self._cls.__qualname__}."
10671065
)
10681066
except AttributeError:
10691067
pass
@@ -1583,12 +1581,10 @@ def _generate_unique_filename(cls, func_name):
15831581
"""
15841582
Create a "filename" suitable for a function being generated.
15851583
"""
1586-
unique_filename = "<attrs generated {} {}.{}>".format(
1587-
func_name,
1588-
cls.__module__,
1589-
getattr(cls, "__qualname__", cls.__name__),
1584+
return (
1585+
f"<attrs generated {func_name} {cls.__module__}."
1586+
f"{getattr(cls, '__qualname__', cls.__name__)}>"
15901587
)
1591-
return unique_filename
15921588

15931589

15941590
def _make_hash(cls, attrs, frozen, cache_hash):
@@ -1630,34 +1626,34 @@ def append_hash_computation_lines(prefix, indent):
16301626
method_lines.extend(
16311627
[
16321628
indent + prefix + hash_func,
1633-
indent + " %d," % (type_hash,),
1629+
indent + f" {type_hash},",
16341630
]
16351631
)
16361632

16371633
for a in attrs:
16381634
if a.eq_key:
1639-
cmp_name = "_%s_key" % (a.name,)
1635+
cmp_name = f"_{a.name}_key"
16401636
globs[cmp_name] = a.eq_key
16411637
method_lines.append(
1642-
indent + " %s(self.%s)," % (cmp_name, a.name)
1638+
indent + f" {cmp_name}(self.{a.name}),"
16431639
)
16441640
else:
1645-
method_lines.append(indent + " self.%s," % a.name)
1641+
method_lines.append(indent + f" self.{a.name},")
16461642

16471643
method_lines.append(indent + " " + closing_braces)
16481644

16491645
if cache_hash:
1650-
method_lines.append(tab + "if self.%s is None:" % _hash_cache_field)
1646+
method_lines.append(tab + f"if self.{_hash_cache_field} is None:")
16511647
if frozen:
16521648
append_hash_computation_lines(
1653-
"object.__setattr__(self, '%s', " % _hash_cache_field, tab * 2
1649+
f"object.__setattr__(self, '{_hash_cache_field}', ", tab * 2
16541650
)
16551651
method_lines.append(tab * 2 + ")") # close __setattr__
16561652
else:
16571653
append_hash_computation_lines(
1658-
"self.%s = " % _hash_cache_field, tab * 2
1654+
f"self.{_hash_cache_field} = ", tab * 2
16591655
)
1660-
method_lines.append(tab + "return self.%s" % _hash_cache_field)
1656+
method_lines.append(tab + f"return self.{_hash_cache_field}")
16611657
else:
16621658
append_hash_computation_lines("return ", tab)
16631659

@@ -1713,27 +1709,15 @@ def _make_eq(cls, attrs):
17131709
others = [" ) == ("]
17141710
for a in attrs:
17151711
if a.eq_key:
1716-
cmp_name = "_%s_key" % (a.name,)
1712+
cmp_name = f"_{a.name}_key"
17171713
# Add the key function to the global namespace
17181714
# of the evaluated function.
17191715
globs[cmp_name] = a.eq_key
1720-
lines.append(
1721-
" %s(self.%s),"
1722-
% (
1723-
cmp_name,
1724-
a.name,
1725-
)
1726-
)
1727-
others.append(
1728-
" %s(other.%s),"
1729-
% (
1730-
cmp_name,
1731-
a.name,
1732-
)
1733-
)
1716+
lines.append(f" {cmp_name}(self.{a.name}),")
1717+
others.append(f" {cmp_name}(other.{a.name}),")
17341718
else:
1735-
lines.append(" self.%s," % (a.name,))
1736-
others.append(" other.%s," % (a.name,))
1719+
lines.append(f" self.{a.name},")
1720+
others.append(f" other.{a.name},")
17371721

17381722
lines += others + [" )"]
17391723
else:
@@ -1860,7 +1844,7 @@ def _make_repr(attrs, ns, cls):
18601844
" else:",
18611845
" already_repring.add(id(self))",
18621846
" try:",
1863-
" return f'%s(%s)'" % (cls_name_fragment, repr_fragment),
1847+
f" return f'{cls_name_fragment}({repr_fragment})'",
18641848
" finally:",
18651849
" already_repring.remove(id(self))",
18661850
]
@@ -2037,7 +2021,7 @@ def _setattr(attr_name, value_var, has_on_setattr):
20372021
"""
20382022
Use the cached object.setattr to set *attr_name* to *value_var*.
20392023
"""
2040-
return "_setattr('%s', %s)" % (attr_name, value_var)
2024+
return f"_setattr('{attr_name}', {value_var})"
20412025

20422026

20432027
def _setattr_with_converter(attr_name, value_var, has_on_setattr):
@@ -2060,7 +2044,7 @@ def _assign(attr_name, value, has_on_setattr):
20602044
if has_on_setattr:
20612045
return _setattr(attr_name, value, True)
20622046

2063-
return "self.%s = %s" % (attr_name, value)
2047+
return f"self.{attr_name} = {value}"
20642048

20652049

20662050
def _assign_with_converter(attr_name, value_var, has_on_setattr):
@@ -2126,7 +2110,7 @@ def fmt_setter(attr_name, value_var, has_on_setattr):
21262110
if _is_slot_attr(attr_name, base_attr_map):
21272111
return _setattr(attr_name, value_var, has_on_setattr)
21282112

2129-
return "_inst_dict['%s'] = %s" % (attr_name, value_var)
2113+
return f"_inst_dict['{attr_name}'] = {value_var}"
21302114

21312115
def fmt_setter_with_converter(
21322116
attr_name, value_var, has_on_setattr
@@ -2174,12 +2158,12 @@ def fmt_setter_with_converter(
21742158

21752159
if a.init is False:
21762160
if has_factory:
2177-
init_factory_name = _init_factory_pat.format(a.name)
2161+
init_factory_name = _init_factory_pat % (a.name,)
21782162
if a.converter is not None:
21792163
lines.append(
21802164
fmt_setter_with_converter(
21812165
attr_name,
2182-
init_factory_name + "(%s)" % (maybe_self,),
2166+
init_factory_name + f"({maybe_self})",
21832167
has_on_setattr,
21842168
)
21852169
)
@@ -2189,7 +2173,7 @@ def fmt_setter_with_converter(
21892173
lines.append(
21902174
fmt_setter(
21912175
attr_name,
2192-
init_factory_name + "(%s)" % (maybe_self,),
2176+
init_factory_name + f"({maybe_self})",
21932177
has_on_setattr,
21942178
)
21952179
)
@@ -2199,7 +2183,7 @@ def fmt_setter_with_converter(
21992183
lines.append(
22002184
fmt_setter_with_converter(
22012185
attr_name,
2202-
"attr_dict['%s'].default" % (attr_name,),
2186+
f"attr_dict['{attr_name}'].default",
22032187
has_on_setattr,
22042188
)
22052189
)
@@ -2209,12 +2193,12 @@ def fmt_setter_with_converter(
22092193
lines.append(
22102194
fmt_setter(
22112195
attr_name,
2212-
"attr_dict['%s'].default" % (attr_name,),
2196+
f"attr_dict['{attr_name}'].default",
22132197
has_on_setattr,
22142198
)
22152199
)
22162200
elif a.default is not NOTHING and not has_factory:
2217-
arg = "%s=attr_dict['%s'].default" % (arg_name, attr_name)
2201+
arg = f"{arg_name}=attr_dict['{attr_name}'].default"
22182202
if a.kw_only:
22192203
kw_only_args.append(arg)
22202204
else:
@@ -2233,14 +2217,14 @@ def fmt_setter_with_converter(
22332217
lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))
22342218

22352219
elif has_factory:
2236-
arg = "%s=NOTHING" % (arg_name,)
2220+
arg = f"{arg_name}=NOTHING"
22372221
if a.kw_only:
22382222
kw_only_args.append(arg)
22392223
else:
22402224
args.append(arg)
2241-
lines.append("if %s is not NOTHING:" % (arg_name,))
2225+
lines.append(f"if {arg_name} is not NOTHING:")
22422226

2243-
init_factory_name = _init_factory_pat.format(a.name)
2227+
init_factory_name = _init_factory_pat % (a.name,)
22442228
if a.converter is not None:
22452229
lines.append(
22462230
" "
@@ -2307,9 +2291,7 @@ def fmt_setter_with_converter(
23072291
for a in attrs_to_validate:
23082292
val_name = "__attr_validator_" + a.name
23092293
attr_name = "__attr_" + a.name
2310-
lines.append(
2311-
" %s(self, %s, self.%s)" % (val_name, attr_name, a.name)
2312-
)
2294+
lines.append(f" {val_name}(self, {attr_name}, self.{a.name})")
23132295
names_for_globals[val_name] = a.validator
23142296
names_for_globals[attr_name] = a
23152297

@@ -2336,24 +2318,23 @@ def fmt_setter_with_converter(
23362318
# For exceptions we rely on BaseException.__init__ for proper
23372319
# initialization.
23382320
if is_exc:
2339-
vals = ",".join("self." + a.name for a in attrs if a.init)
2321+
vals = ",".join(f"self.{a.name}" for a in attrs if a.init)
23402322

2341-
lines.append("BaseException.__init__(self, %s)" % (vals,))
2323+
lines.append(f"BaseException.__init__(self, {vals})")
23422324

23432325
args = ", ".join(args)
23442326
if kw_only_args:
23452327
args += "%s*, %s" % (
23462328
", " if args else "", # leading comma
23472329
", ".join(kw_only_args), # kw_only args
23482330
)
2331+
23492332
return (
2350-
"""\
2351-
def {init_name}(self, {args}):
2352-
{lines}
2353-
""".format(
2354-
init_name=("__attrs_init__" if attrs_init else "__init__"),
2355-
args=args,
2356-
lines="\n ".join(lines) if lines else "pass",
2333+
"def %s(self, %s):\n %s\n"
2334+
% (
2335+
("__attrs_init__" if attrs_init else "__init__"),
2336+
args,
2337+
"\n ".join(lines) if lines else "pass",
23572338
),
23582339
names_for_globals,
23592340
annotations,

tests/test_make.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1910,9 +1910,7 @@ class A:
19101910

19111911
if hasattr(A, "__qualname__"):
19121912
method = getattr(A, meth_name)
1913-
expected = "Method generated by attrs for class {}.".format(
1914-
A.__qualname__
1915-
)
1913+
expected = f"Method generated by attrs for class {A.__qualname__}."
19161914
assert expected == method.__doc__
19171915

19181916

0 commit comments

Comments
 (0)