From f4b7a02e932671c6e54e6b48340173cc859ab4c0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 26 Jan 2015 09:57:07 +0200 Subject: Issue #21408: The default __ne__() now returns NotImplemented if __eq__() returned NotImplemented. Removed incorrect implementations of __ne__(). --- Lib/_collections_abc.py | 6 ----- Lib/doctest.py | 9 ------- Lib/lib2to3/pytree.py | 10 -------- Lib/numbers.py | 5 ---- Lib/pathlib.py | 3 --- Lib/test/test_binop.py | 4 --- Lib/test/test_compare.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++-- Lib/unittest/case.py | 3 --- Lib/unittest/suite.py | 3 --- Misc/NEWS | 7 ++++++ Objects/typeobject.c | 9 +++++-- 11 files changed, 77 insertions(+), 47 deletions(-) diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 294a7b1..33b59ab 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -224,9 +224,6 @@ class Set(Sized, Iterable, Container): return NotImplemented return len(self) == len(other) and self.__le__(other) - def __ne__(self, other): - return not (self == other) - @classmethod def _from_iterable(cls, it): '''Construct an instance of the class from any iterable input. @@ -451,9 +448,6 @@ class Mapping(Sized, Iterable, Container): return NotImplemented return dict(self.items()) == dict(other.items()) - def __ne__(self, other): - return not (self == other) - Mapping.register(mappingproxy) diff --git a/Lib/doctest.py b/Lib/doctest.py index d212ad6..64e6d71 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -481,9 +481,6 @@ class Example: self.options == other.options and \ self.exc_msg == other.exc_msg - def __ne__(self, other): - return not self == other - def __hash__(self): return hash((self.source, self.want, self.lineno, self.indent, self.exc_msg)) @@ -547,9 +544,6 @@ class DocTest: self.filename == other.filename and \ self.lineno == other.lineno - def __ne__(self, other): - return not self == other - def __hash__(self): return hash((self.docstring, self.name, self.filename, self.lineno)) @@ -2289,9 +2283,6 @@ class DocTestCase(unittest.TestCase): self._dt_tearDown == other._dt_tearDown and \ self._dt_checker == other._dt_checker - def __ne__(self, other): - return not self == other - def __hash__(self): return hash((self._dt_optionflags, self._dt_setUp, self._dt_tearDown, self._dt_checker)) diff --git a/Lib/lib2to3/pytree.py b/Lib/lib2to3/pytree.py index c4a1be3..ad3592c 100644 --- a/Lib/lib2to3/pytree.py +++ b/Lib/lib2to3/pytree.py @@ -64,16 +64,6 @@ class Base(object): __hash__ = None # For Py3 compatibility. - def __ne__(self, other): - """ - Compare two nodes for inequality. - - This calls the method _eq(). - """ - if self.__class__ is not other.__class__: - return NotImplemented - return not self._eq(other) - def _eq(self, other): """ Compare two nodes for equality. diff --git a/Lib/numbers.py b/Lib/numbers.py index b206457..7eedc63 100644 --- a/Lib/numbers.py +++ b/Lib/numbers.py @@ -141,11 +141,6 @@ class Complex(Number): """self == other""" raise NotImplementedError - def __ne__(self, other): - """self != other""" - # The default __ne__ doesn't negate __eq__ until 3.0. - return not (self == other) - Complex.register(complex) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 5d36364..73fd432 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -665,9 +665,6 @@ class PurePath(object): return NotImplemented return self._cparts == other._cparts and self._flavour is other._flavour - def __ne__(self, other): - return not self == other - def __hash__(self): try: return self._hash diff --git a/Lib/test/test_binop.py b/Lib/test/test_binop.py index 8417916..9c4c18e 100644 --- a/Lib/test/test_binop.py +++ b/Lib/test/test_binop.py @@ -194,10 +194,6 @@ class Rat(object): return float(self) == other return NotImplemented - def __ne__(self, other): - """Compare two Rats for inequality.""" - return not self == other - class RatTestCase(unittest.TestCase): """Unit tests for Rat class and its support utilities.""" diff --git a/Lib/test/test_compare.py b/Lib/test/test_compare.py index ee3794a..a663832 100644 --- a/Lib/test/test_compare.py +++ b/Lib/test/test_compare.py @@ -48,8 +48,69 @@ class ComparisonTest(unittest.TestCase): def test_ne_defaults_to_not_eq(self): a = Cmp(1) b = Cmp(1) - self.assertTrue(a == b) - self.assertFalse(a != b) + c = Cmp(2) + self.assertIs(a == b, True) + self.assertIs(a != b, False) + self.assertIs(a != c, True) + + def test_ne_high_priority(self): + """object.__ne__() should allow reflected __ne__() to be tried""" + calls = [] + class Left: + # Inherits object.__ne__() + def __eq__(*args): + calls.append('Left.__eq__') + return NotImplemented + class Right: + def __eq__(*args): + calls.append('Right.__eq__') + return NotImplemented + def __ne__(*args): + calls.append('Right.__ne__') + return NotImplemented + Left() != Right() + self.assertSequenceEqual(calls, ['Left.__eq__', 'Right.__ne__']) + + def test_ne_low_priority(self): + """object.__ne__() should not invoke reflected __eq__()""" + calls = [] + class Base: + # Inherits object.__ne__() + def __eq__(*args): + calls.append('Base.__eq__') + return NotImplemented + class Derived(Base): # Subclassing forces higher priority + def __eq__(*args): + calls.append('Derived.__eq__') + return NotImplemented + def __ne__(*args): + calls.append('Derived.__ne__') + return NotImplemented + Base() != Derived() + self.assertSequenceEqual(calls, ['Derived.__ne__', 'Base.__eq__']) + + def test_other_delegation(self): + """No default delegation between operations except __ne__()""" + ops = ( + ('__eq__', lambda a, b: a == b), + ('__lt__', lambda a, b: a < b), + ('__le__', lambda a, b: a <= b), + ('__gt__', lambda a, b: a > b), + ('__ge__', lambda a, b: a >= b), + ) + for name, func in ops: + with self.subTest(name): + def unexpected(*args): + self.fail('Unexpected operator method called') + class C: + __ne__ = unexpected + for other, _ in ops: + if other != name: + setattr(C, other, unexpected) + if name == '__eq__': + self.assertIs(func(C(), object()), False) + else: + self.assertRaises(TypeError, func, C(), object()) def test_issue_1393(self): x = lambda: None diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index aa00b7a..69888a5 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -1342,9 +1342,6 @@ class FunctionTestCase(TestCase): self._testFunc == other._testFunc and \ self._description == other._description - def __ne__(self, other): - return not self == other - def __hash__(self): return hash((type(self), self._setUpFunc, self._tearDownFunc, self._testFunc, self._description)) diff --git a/Lib/unittest/suite.py b/Lib/unittest/suite.py index 4997d81..76c4725 100644 --- a/Lib/unittest/suite.py +++ b/Lib/unittest/suite.py @@ -31,9 +31,6 @@ class BaseTestSuite(object): return NotImplemented return list(self) == list(other) - def __ne__(self, other): - return not self == other - def __iter__(self): return iter(self._tests) diff --git a/Misc/NEWS b/Misc/NEWS index 06df4db..6970e80 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -11,6 +11,9 @@ Release date: TBA Core and Builtins ----------------- +- Issue #21408: The default __ne__() now returns NotImplemented if __eq__() + returned NotImplemented. + - Issue #23321: Fixed a crash in str.decode() when error handler returned replacment string longer than mailformed input data. @@ -47,6 +50,10 @@ Core and Builtins Library ------- +- Issue #21408: Removed incorrect implementations of __ne__() which didn't + returned NotImplemented if __eq__() returned NotImplemented. The default + __ne__() now works correctly. + - Issue #19996: :class:`email.feedparser.FeedParser` now handles (malformed) headers with no key rather than amusing the body has started. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 3b1d189..1d98fc2 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3348,9 +3348,14 @@ object_richcompare(PyObject *self, PyObject *other, int op) break; case Py_NE: - /* By default, != returns the opposite of ==, + /* By default, __ne__() delegates to __eq__() and inverts the result, unless the latter returns NotImplemented. */ - res = PyObject_RichCompare(self, other, Py_EQ); + if (self->ob_type->tp_richcompare == NULL) { + res = Py_NotImplemented; + Py_INCREF(res); + break; + } + res = (*self->ob_type->tp_richcompare)(self, other, Py_EQ); if (res != NULL && res != Py_NotImplemented) { int ok = PyObject_IsTrue(res); Py_DECREF(res); -- cgit v0.12