diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 60b471317ce97c..da16c72efccf6c 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -594,6 +594,8 @@ def __eq__(self, other): return NotImplemented return len(self) == len(other) and self.__le__(other) + __ne__ = object.__ne__ + @classmethod def _from_iterable(cls, it): '''Construct an instance of the class from any iterable input. @@ -821,6 +823,8 @@ def __eq__(self, other): return NotImplemented return dict(self.items()) == dict(other.items()) + __ne__ = object.__ne__ + __reversed__ = None Mapping.register(mappingproxy) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 22595239252814..2a2dae49306657 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -751,7 +751,8 @@ def validate_isinstance(self, abc, name): self.assertNotIsInstance(C(), abc) self.assertNotIsSubclass(C, abc) - def validate_comparison(self, instance): + def validate_comparison(self, klass): + instance = klass() ops = ['lt', 'gt', 'le', 'ge', 'ne', 'or', 'and', 'xor', 'sub'] operators = {} for op in ops: @@ -782,6 +783,21 @@ def __eq__(self, other): self.assertTrue(other.right_side,'Right side not called for %s.%s' % (type(instance), name)) + # gh-85588: Inherited __ne__ should be overriddent together with __eq__ + # in Set and Mapping. + class Mixin: + def __eq__(self, other): + raise AssertionError('should not be called') + __ne__ = __eq__ + class C(klass, Mixin): + pass + instance = C() + other = object() + self.assertIs(instance == other, False) + self.assertIs(instance != other, True) + self.assertIs(instance.__eq__(other), NotImplemented) + self.assertIs(instance.__ne__(other), NotImplemented) + def _test_gen(): yield @@ -1430,7 +1446,7 @@ def __len__(self): return 0 def __iter__(self): return iter([]) - self.validate_comparison(MySet()) + self.validate_comparison(MySet) def test_hash_Set(self): class OneTwoThreeSet(Set): @@ -1852,7 +1868,7 @@ def __getitem__(self, i): raise IndexError def __iter__(self): return iter(()) - self.validate_comparison(MyMapping()) + self.validate_comparison(MyMapping) self.assertRaises(TypeError, reversed, MyMapping()) def test_MutableMapping(self): diff --git a/Misc/NEWS.d/next/Library/2025-11-10-18-38-58.gh-issue-85588.562vvi.rst b/Misc/NEWS.d/next/Library/2025-11-10-18-38-58.gh-issue-85588.562vvi.rst new file mode 100644 index 00000000000000..49a2f4a6ed3ffc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-11-10-18-38-58.gh-issue-85588.562vvi.rst @@ -0,0 +1,5 @@ +Restore the :meth:`!__ne__` method (identical to :meth:`object.__ne__`) in +:class:`collections.abc.Set` and :class:`collections.abc.Mapping` classes. +This guarantees that the ``!=`` operator is consistent with the ``==`` +operator, even if the :meth:`!__ne__` method was defined in other parent +class.