From 375b728e148794f4b84fad422c99ed225fd534f3 Mon Sep 17 00:00:00 2001 From: Ray Zhang Date: Fri, 14 Jan 2022 16:55:57 -0500 Subject: [PATCH 1/7] Add hashing for eq'd objects --- src/attr/_make.py | 11 +++++++++-- tests/test_make.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/attr/_make.py b/src/attr/_make.py index 915c5e663..af271088b 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -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((" @@ -1714,7 +1716,12 @@ 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) @@ -1734,7 +1741,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): diff --git a/tests/test_make.py b/tests/test_make.py index 6cc4f059f..961c9a073 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -2057,6 +2057,21 @@ 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) + # These hashes should be the same because 1 is turned into + # string before hashing. + assert hash(C("1")) == hash(C(1)) + + @pytest.mark.parametrize("slots", [True, False]) @pytest.mark.parametrize("frozen", [True, False]) def test_detect_auto_hash(self, slots, frozen): From 59f6371aa2234b499ebf02bf845d417695e6ca5d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 14 Jan 2022 22:02:30 +0000 Subject: [PATCH 2/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/attr/_make.py | 4 +++- tests/test_make.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/attr/_make.py b/src/attr/_make.py index af271088b..962e560ce 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -1719,7 +1719,9 @@ def append_hash_computation_lines(prefix, indent): 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)) + method_lines.append( + indent + " %s(self.%s)," % (cmp_name, a.name) + ) else: method_lines.append(indent + " self.%s," % a.name) diff --git a/tests/test_make.py b/tests/test_make.py index 961c9a073..74f82de1e 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -2064,14 +2064,15 @@ 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) + # These hashes should be the same because 1 is turned into # string before hashing. assert hash(C("1")) == hash(C(1)) - @pytest.mark.parametrize("slots", [True, False]) @pytest.mark.parametrize("frozen", [True, False]) def test_detect_auto_hash(self, slots, frozen): From d7c49cf74822221e90f15bd88e8a7280e1af96fd Mon Sep 17 00:00:00 2001 From: Ray Zhang Date: Fri, 14 Jan 2022 17:15:22 -0500 Subject: [PATCH 3/7] Add coverage to test --- tests/test_make.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_make.py b/tests/test_make.py index 74f82de1e..2a82bae5f 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -2069,9 +2069,14 @@ def test_hash_uses_eq(self, slots, frozen): class C(object): x = attr.ib(eq=str) + @attr.s(slots=slots, frozen=frozen, hash=True) + class D(object): + x = attr.ib(eq=str) + # 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]) From 34ae15650cd30df74e5a0590bf606927e2449b71 Mon Sep 17 00:00:00 2001 From: Ray Zhang Date: Fri, 14 Jan 2022 17:17:49 -0500 Subject: [PATCH 4/7] oops --- tests/test_make.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_make.py b/tests/test_make.py index 2a82bae5f..cdca30d08 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -2071,7 +2071,7 @@ class C(object): @attr.s(slots=slots, frozen=frozen, hash=True) class D(object): - x = attr.ib(eq=str) + x = attr.ib() # These hashes should be the same because 1 is turned into # string before hashing. From 0bd3f5cba641046e6b3342093301c755835674d8 Mon Sep 17 00:00:00 2001 From: Ray Zhang Date: Sun, 23 Jan 2022 18:11:27 -0500 Subject: [PATCH 5/7] Add changelog message --- changelog.d/898.change.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.d/898.change.rst b/changelog.d/898.change.rst index bbd2d8bee..65a4d527f 100644 --- a/changelog.d/898.change.rst +++ b/changelog.d/898.change.rst @@ -1 +1,2 @@ Speedup instantiation of frozen slotted classes. +If an eq key is defined, it is also used before hashing the attribute. From 249f5a6988b5cdac7c10c2c150e358c972ae4b68 Mon Sep 17 00:00:00 2001 From: Ray Zhang Date: Sun, 30 Jan 2022 15:21:52 -0500 Subject: [PATCH 6/7] Revert "Add changelog message" This reverts commit 0bd3f5cba641046e6b3342093301c755835674d8. --- changelog.d/898.change.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.d/898.change.rst b/changelog.d/898.change.rst index 65a4d527f..bbd2d8bee 100644 --- a/changelog.d/898.change.rst +++ b/changelog.d/898.change.rst @@ -1,2 +1 @@ Speedup instantiation of frozen slotted classes. -If an eq key is defined, it is also used before hashing the attribute. From a6ae085ef5847a27fc960540aac404242515c7e4 Mon Sep 17 00:00:00 2001 From: Ray Zhang Date: Sun, 30 Jan 2022 15:24:37 -0500 Subject: [PATCH 7/7] Address comments --- changelog.d/909.change.rst | 1 + src/attr/_make.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 changelog.d/909.change.rst diff --git a/changelog.d/909.change.rst b/changelog.d/909.change.rst new file mode 100644 index 000000000..359d1207b --- /dev/null +++ b/changelog.d/909.change.rst @@ -0,0 +1 @@ +If an eq key is defined, it is also used before hashing the attribute. diff --git a/src/attr/_make.py b/src/attr/_make.py index 962e560ce..19a354204 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -325,13 +325,11 @@ def _compile_and_eval(script, globs, locs=None, filename=""): eval(bytecode, globs, locs) -def _make_method(name, script, filename, globs=None): +def _make_method(name, script, filename, globs): """ Create the method with the script given and return the method object. """ locs = {} - if globs is None: - globs = {} # In order of debuggers like PDB being able to step through the code, # we add a fake linecache entry.