Skip to content

Commit

Permalink
Eliminate most str.format() and %-formatting (#995)
Browse files Browse the repository at this point in the history
* Eliminate most str.format() and %-formatting

* Add newsfragment
  • Loading branch information
hynek committed Aug 10, 2022
1 parent a819155 commit 6151683
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 73 deletions.
3 changes: 3 additions & 0 deletions changelog.d/995.change.rst
@@ -0,0 +1,3 @@
Class-creation performance improvements by switching performance-sensitive templating operations to f-strings.

You can expect an improvement of about 5% even for very simple classes.
2 changes: 1 addition & 1 deletion docs/why.rst
Expand Up @@ -218,7 +218,7 @@ is roughly
... self.b = b
...
... def __repr__(self):
... return "ArtisanalClass(a={}, b={})".format(self.a, self.b)
... return f"ArtisanalClass(a={self.a}, b={self.b})"
...
... def __eq__(self, other):
... if other.__class__ is self.__class__:
Expand Down
6 changes: 3 additions & 3 deletions src/attr/_cmp.py
Expand Up @@ -130,9 +130,9 @@ def method(self, other):

return result

method.__name__ = "__%s__" % (name,)
method.__doc__ = "Return a %s b. Computed by attrs." % (
_operation_names[name],
method.__name__ = f"__{name}__"
method.__doc__ = (
f"Return a {_operation_names[name]} b. Computed by attrs."
)

return method
Expand Down
4 changes: 1 addition & 3 deletions src/attr/_funcs.py
Expand Up @@ -331,9 +331,7 @@ def assoc(inst, **changes):
a = getattr(attrs, k, NOTHING)
if a is NOTHING:
raise AttrsAttributeNotFoundError(
"{k} is not an attrs attribute on {cl}.".format(
k=k, cl=new.__class__
)
f"{k} is not an attrs attribute on {new.__class__}."
)
_obj_setattr(new, k, v)
return new
Expand Down
107 changes: 44 additions & 63 deletions src/attr/_make.py
Expand Up @@ -23,10 +23,7 @@
# This is used at least twice, so cache it here.
_obj_setattr = object.__setattr__
_init_converter_pat = "__attr_converter_%s"
_init_factory_pat = "__attr_factory_{}"
_tuple_property_pat = (
" {attr_name} = _attrs_property(_attrs_itemgetter({index}))"
)
_init_factory_pat = "__attr_factory_%s"
_classvar_prefixes = (
"typing.ClassVar",
"t.ClassVar",
Expand Down Expand Up @@ -342,7 +339,7 @@ class MyClassAttributes(tuple):
if attr_names:
for i, attr_name in enumerate(attr_names):
attr_class_template.append(
_tuple_property_pat.format(index=i, attr_name=attr_name)
f" {attr_name} = _attrs_property(_attrs_itemgetter({i}))"
)
else:
attr_class_template.append(" pass")
Expand Down Expand Up @@ -559,7 +556,7 @@ def _transform_attrs(
if had_default is True and a.default is NOTHING:
raise ValueError(
"No mandatory attributes allowed after an attribute with a "
"default value or factory. Attribute in question: %r" % (a,)
f"default value or factory. Attribute in question: {a!r}"
)

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

try:
method.__doc__ = "Method generated by attrs for class %s." % (
self._cls.__qualname__,
method.__doc__ = (
"Method generated by attrs for class "
f"{self._cls.__qualname__}."
)
except AttributeError:
pass
Expand Down Expand Up @@ -1583,12 +1581,10 @@ def _generate_unique_filename(cls, func_name):
"""
Create a "filename" suitable for a function being generated.
"""
unique_filename = "<attrs generated {} {}.{}>".format(
func_name,
cls.__module__,
getattr(cls, "__qualname__", cls.__name__),
return (
f"<attrs generated {func_name} {cls.__module__}."
f"{getattr(cls, '__qualname__', cls.__name__)}>"
)
return unique_filename


def _make_hash(cls, attrs, frozen, cache_hash):
Expand Down Expand Up @@ -1630,34 +1626,34 @@ def append_hash_computation_lines(prefix, indent):
method_lines.extend(
[
indent + prefix + hash_func,
indent + " %d," % (type_hash,),
indent + f" {type_hash},",
]
)

for a in attrs:
if a.eq_key:
cmp_name = "_%s_key" % (a.name,)
cmp_name = f"_{a.name}_key"
globs[cmp_name] = a.eq_key
method_lines.append(
indent + " %s(self.%s)," % (cmp_name, a.name)
indent + f" {cmp_name}(self.{a.name}),"
)
else:
method_lines.append(indent + " self.%s," % a.name)
method_lines.append(indent + f" self.{a.name},")

method_lines.append(indent + " " + closing_braces)

if cache_hash:
method_lines.append(tab + "if self.%s is None:" % _hash_cache_field)
method_lines.append(tab + f"if self.{_hash_cache_field} is None:")
if frozen:
append_hash_computation_lines(
"object.__setattr__(self, '%s', " % _hash_cache_field, tab * 2
f"object.__setattr__(self, '{_hash_cache_field}', ", tab * 2
)
method_lines.append(tab * 2 + ")") # close __setattr__
else:
append_hash_computation_lines(
"self.%s = " % _hash_cache_field, tab * 2
f"self.{_hash_cache_field} = ", tab * 2
)
method_lines.append(tab + "return self.%s" % _hash_cache_field)
method_lines.append(tab + f"return self.{_hash_cache_field}")
else:
append_hash_computation_lines("return ", tab)

Expand Down Expand Up @@ -1713,27 +1709,15 @@ def _make_eq(cls, attrs):
others = [" ) == ("]
for a in attrs:
if a.eq_key:
cmp_name = "_%s_key" % (a.name,)
cmp_name = f"_{a.name}_key"
# Add the key function to the global namespace
# of the evaluated function.
globs[cmp_name] = a.eq_key
lines.append(
" %s(self.%s),"
% (
cmp_name,
a.name,
)
)
others.append(
" %s(other.%s),"
% (
cmp_name,
a.name,
)
)
lines.append(f" {cmp_name}(self.{a.name}),")
others.append(f" {cmp_name}(other.{a.name}),")
else:
lines.append(" self.%s," % (a.name,))
others.append(" other.%s," % (a.name,))
lines.append(f" self.{a.name},")
others.append(f" other.{a.name},")

lines += others + [" )"]
else:
Expand Down Expand Up @@ -1860,7 +1844,7 @@ def _make_repr(attrs, ns, cls):
" else:",
" already_repring.add(id(self))",
" try:",
" return f'%s(%s)'" % (cls_name_fragment, repr_fragment),
f" return f'{cls_name_fragment}({repr_fragment})'",
" finally:",
" already_repring.remove(id(self))",
]
Expand Down Expand Up @@ -2037,7 +2021,7 @@ def _setattr(attr_name, value_var, has_on_setattr):
"""
Use the cached object.setattr to set *attr_name* to *value_var*.
"""
return "_setattr('%s', %s)" % (attr_name, value_var)
return f"_setattr('{attr_name}', {value_var})"


def _setattr_with_converter(attr_name, value_var, has_on_setattr):
Expand All @@ -2060,7 +2044,7 @@ def _assign(attr_name, value, has_on_setattr):
if has_on_setattr:
return _setattr(attr_name, value, True)

return "self.%s = %s" % (attr_name, value)
return f"self.{attr_name} = {value}"


def _assign_with_converter(attr_name, value_var, has_on_setattr):
Expand Down Expand Up @@ -2126,7 +2110,7 @@ def fmt_setter(attr_name, value_var, has_on_setattr):
if _is_slot_attr(attr_name, base_attr_map):
return _setattr(attr_name, value_var, has_on_setattr)

return "_inst_dict['%s'] = %s" % (attr_name, value_var)
return f"_inst_dict['{attr_name}'] = {value_var}"

def fmt_setter_with_converter(
attr_name, value_var, has_on_setattr
Expand Down Expand Up @@ -2174,12 +2158,12 @@ def fmt_setter_with_converter(

if a.init is False:
if has_factory:
init_factory_name = _init_factory_pat.format(a.name)
init_factory_name = _init_factory_pat % (a.name,)
if a.converter is not None:
lines.append(
fmt_setter_with_converter(
attr_name,
init_factory_name + "(%s)" % (maybe_self,),
init_factory_name + f"({maybe_self})",
has_on_setattr,
)
)
Expand All @@ -2189,7 +2173,7 @@ def fmt_setter_with_converter(
lines.append(
fmt_setter(
attr_name,
init_factory_name + "(%s)" % (maybe_self,),
init_factory_name + f"({maybe_self})",
has_on_setattr,
)
)
Expand All @@ -2199,7 +2183,7 @@ def fmt_setter_with_converter(
lines.append(
fmt_setter_with_converter(
attr_name,
"attr_dict['%s'].default" % (attr_name,),
f"attr_dict['{attr_name}'].default",
has_on_setattr,
)
)
Expand All @@ -2209,12 +2193,12 @@ def fmt_setter_with_converter(
lines.append(
fmt_setter(
attr_name,
"attr_dict['%s'].default" % (attr_name,),
f"attr_dict['{attr_name}'].default",
has_on_setattr,
)
)
elif a.default is not NOTHING and not has_factory:
arg = "%s=attr_dict['%s'].default" % (arg_name, attr_name)
arg = f"{arg_name}=attr_dict['{attr_name}'].default"
if a.kw_only:
kw_only_args.append(arg)
else:
Expand All @@ -2233,14 +2217,14 @@ def fmt_setter_with_converter(
lines.append(fmt_setter(attr_name, arg_name, has_on_setattr))

elif has_factory:
arg = "%s=NOTHING" % (arg_name,)
arg = f"{arg_name}=NOTHING"
if a.kw_only:
kw_only_args.append(arg)
else:
args.append(arg)
lines.append("if %s is not NOTHING:" % (arg_name,))
lines.append(f"if {arg_name} is not NOTHING:")

init_factory_name = _init_factory_pat.format(a.name)
init_factory_name = _init_factory_pat % (a.name,)
if a.converter is not None:
lines.append(
" "
Expand Down Expand Up @@ -2307,9 +2291,7 @@ def fmt_setter_with_converter(
for a in attrs_to_validate:
val_name = "__attr_validator_" + a.name
attr_name = "__attr_" + a.name
lines.append(
" %s(self, %s, self.%s)" % (val_name, attr_name, a.name)
)
lines.append(f" {val_name}(self, {attr_name}, self.{a.name})")
names_for_globals[val_name] = a.validator
names_for_globals[attr_name] = a

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

lines.append("BaseException.__init__(self, %s)" % (vals,))
lines.append(f"BaseException.__init__(self, {vals})")

args = ", ".join(args)
if kw_only_args:
args += "%s*, %s" % (
", " if args else "", # leading comma
", ".join(kw_only_args), # kw_only args
)

return (
"""\
def {init_name}(self, {args}):
{lines}
""".format(
init_name=("__attrs_init__" if attrs_init else "__init__"),
args=args,
lines="\n ".join(lines) if lines else "pass",
"def %s(self, %s):\n %s\n"
% (
("__attrs_init__" if attrs_init else "__init__"),
args,
"\n ".join(lines) if lines else "pass",
),
names_for_globals,
annotations,
Expand Down
4 changes: 1 addition & 3 deletions tests/test_make.py
Expand Up @@ -1910,9 +1910,7 @@ class A:

if hasattr(A, "__qualname__"):
method = getattr(A, meth_name)
expected = "Method generated by attrs for class {}.".format(
A.__qualname__
)
expected = f"Method generated by attrs for class {A.__qualname__}."
assert expected == method.__doc__


Expand Down

0 comments on commit 6151683

Please sign in to comment.