diff --git a/mutagen/_util.py b/mutagen/_util.py index 62077d40..e7b45fef 100644 --- a/mutagen/_util.py +++ b/mutagen/_util.py @@ -522,7 +522,11 @@ def __eq__(self, other): def __lt__(self, other): return dict(self.items()) < other - __hash__ = object.__hash__ + # DictMixin overrides __eq__ with content comparison, so objects with + # equal content but different identities would compare equal but hash + # differently when using object.__hash__. Since these tag objects are + # mutable, make them explicitly unhashable (like Python's built-in dict). + __hash__ = None # type: ignore[assignment] def __len__(self): return len(self.keys()) diff --git a/tests/test__util.py b/tests/test__util.py index de2865da..da76e22d 100644 --- a/tests/test__util.py +++ b/tests/test__util.py @@ -123,6 +123,12 @@ def test_repr(self): def test_len(self): self.failUnlessEqual(len(self.rdict), len(self.fdict)) + def test_unhashable(self): + # DictMixin defines __eq__ by content, so objects with equal content + # would hash differently under object.__hash__ (identity). The class + # must be unhashable to respect Python's hash/equality invariant. + self.failUnlessRaises(TypeError, hash, self.fdict) + def tearDown(self): self.failUnlessEqual(self.fdict, self.rdict) self.failUnlessEqual(self.rdict, self.fdict)