Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
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