From cb6068ae647fe6baf5e9283385e4eb8c38cb980b Mon Sep 17 00:00:00 2001 From: Nathaniel Starkman Date: Tue, 14 Apr 2026 01:40:02 -0400 Subject: [PATCH] Backport PR #50: Fix for Inconsistent equality and hashing --- src/xmmutablemap/_core.py | 19 ++++++++++++++++++- tests/test_immutablemap.py | 23 ++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/xmmutablemap/_core.py b/src/xmmutablemap/_core.py index 3632880..544aa8e 100644 --- a/src/xmmutablemap/_core.py +++ b/src/xmmutablemap/_core.py @@ -89,6 +89,23 @@ def __len__(self) -> int: """ return len(self._data) + def __eq__(self, other: object) -> bool: + """Return whether two mappings contain the same items.""" + if isinstance(other, ImmutableMap): + return self._data == other._data + if isinstance(other, Mapping): + if len(self._data) != len(other): + return False + for key, value in other.items(): + try: + self_value = self._data[key] + except KeyError: + return False + if self_value != value: + return False + return True + return NotImplemented + # =========================================== # Mapping Protocol @@ -215,7 +232,7 @@ def __hash__(self) -> int: True """ - return hash(tuple(self._data.items())) + return hash(frozenset(self._data.items())) def __repr__(self) -> str: """Return the representation. diff --git a/tests/test_immutablemap.py b/tests/test_immutablemap.py index 766457a..c0a5efc 100644 --- a/tests/test_immutablemap.py +++ b/tests/test_immutablemap.py @@ -57,13 +57,34 @@ def test_len(self, d: ImmutableMap[str, Any]) -> None: def test_hash(self, d: ImmutableMap[str, Any]) -> None: """Test `__hash__`.""" - assert hash(d) == hash(tuple(d.items())) + assert hash(d) == hash(frozenset(d.items())) # Not hashable if values aren't hashable. d = ImmutableMap(a=1, b={"c"}) with pytest.raises(TypeError, match="unhashable type: 'set'"): hash(d) + def test_eq_with_other_mappings(self) -> None: + """Test mapping interoperability for `__eq__`.""" + d = ImmutableMap(a=1, b=2) + other_dict = {"a": 1, "b": 2} + other_ordered_dict = OrderedDict([("a", 1), ("b", 2)]) + other_proxy = MappingProxyType({"a": 1, "b": 2}) + + assert d == {"a": 1, "b": 2} + assert d == OrderedDict([("a", 1), ("b", 2)]) + assert d == MappingProxyType({"a": 1, "b": 2}) + assert other_dict == d + assert other_ordered_dict == d + assert other_proxy == d + + def test_eq_and_hash_ignore_insertion_order(self) -> None: + """Test equality/hash contract for same items in different orders.""" + d1 = ImmutableMap(a=1, b=2) + d2 = ImmutableMap(b=2, a=1) + assert d1 == d2 + assert hash(d1) == hash(d2) + def test_keys(self, d: ImmutableMap[str, Any]) -> None: """Test `keys`.""" assert list(d.keys()) == ["a", "b"]