Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eliminate most str.format() and %-formatting #995

Merged
merged 2 commits into from Aug 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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