# HG changeset patch # Parent 63dac5212552fba4023f963b107ccf750ab236ea Fix object.__ne__() to call __eq__() rather than evaluate “self == other” Adds some test, including some extra ones to validate the documentation in Issue 4395. diff -r 63dac5212552 Lib/test/test_compare.py --- a/Lib/test/test_compare.py Sat Jan 10 09:00:20 2015 +0100 +++ b/Lib/test/test_compare.py Sun Jan 11 06:23:50 2015 +0000 @@ -48,8 +48,68 @@ def test_ne_defaults_to_not_eq(self): a = Cmp(1) b = Cmp(1) - self.assertTrue(a == b) - self.assertFalse(a != b) + self.assertIs(True, a == b) + self.assertIs(False, a != b) + self.assertIs(True, Cmp(1) != Cmp(2)) + + def test_ne_high_priority(self): + """object.__ne__() should allow reflected __ne__() to be tried""" + calls = list() + class Left(object): + # Inherits object.__ne__() + def __eq__(*args): + calls.append('Left.__eq__') + return NotImplemented + class Right(object): + def __eq__(*args): + calls.append('Right.__eq__') + return NotImplemented + def __ne__(*args): + calls.append('Right.__ne__') + return NotImplemented + Left() != Right() + self.assertSequenceEqual(('Left.__eq__', 'Right.__ne__'), calls) + + def test_ne_low_priority(self): + """object.__ne__() should not invoke reflected __eq__()""" + calls = list() + class Base(object): + # 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(('Derived.__ne__', 'Base.__eq__'), calls) + + 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(False, func(C(), object())) + else: + self.assertRaises(TypeError, func, C(), object()) def test_issue_1393(self): x = lambda: None diff -r 63dac5212552 Objects/typeobject.c --- a/Objects/typeobject.c Sat Jan 10 09:00:20 2015 +0100 +++ b/Objects/typeobject.c Sun Jan 11 06:23:50 2015 +0000 @@ -3385,9 +3385,14 @@ 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);