Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/898.change.rst
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
Speedup instantiation of frozen slotted classes.
If an eq key is defined, it is also used before hashing the attribute.
13 changes: 11 additions & 2 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -1680,6 +1680,8 @@ def _make_hash(cls, attrs, frozen, cache_hash):

unique_filename = _generate_unique_filename(cls, "hash")
type_hash = hash(unique_filename)
# If eq is custom generated, we need to include the functions in globs
globs = {}

hash_def = "def __hash__(self"
hash_func = "hash(("
Expand Down Expand Up @@ -1714,7 +1716,14 @@ def append_hash_computation_lines(prefix, indent):
)

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

method_lines.append(indent + " " + closing_braces)

Expand All @@ -1734,7 +1743,7 @@ def append_hash_computation_lines(prefix, indent):
append_hash_computation_lines("return ", tab)

script = "\n".join(method_lines)
return _make_method("__hash__", script, unique_filename)
return _make_method("__hash__", script, unique_filename, globs)


def _add_hash(cls, attrs):
Expand Down
21 changes: 21 additions & 0 deletions tests/test_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -2057,6 +2057,27 @@ def __repr__(self):

assert "hi" == repr(C(42))

@pytest.mark.parametrize("slots", [True, False])
@pytest.mark.parametrize("frozen", [True, False])
def test_hash_uses_eq(self, slots, frozen):
"""
If eq is passed in, then __hash__ should use the eq callable
to generate the hash code.
"""

@attr.s(slots=slots, frozen=frozen, hash=True)
class C(object):
x = attr.ib(eq=str)

@attr.s(slots=slots, frozen=frozen, hash=True)
class D(object):
x = attr.ib()

# These hashes should be the same because 1 is turned into
# string before hashing.
assert hash(C("1")) == hash(C(1))
assert hash(D("1")) != hash(D(1))

@pytest.mark.parametrize("slots", [True, False])
@pytest.mark.parametrize("frozen", [True, False])
def test_detect_auto_hash(self, slots, frozen):
Expand Down