Skip to content

Commit 18b711c

Browse files
bpo-37648: Fixed minor inconsistency in some __contains__. (GH-14904)
The collection's item is now always at the left and the needle is on the right of ==.
1 parent 17e5264 commit 18b711c

File tree

12 files changed

+86
-22
lines changed

12 files changed

+86
-22
lines changed

Doc/library/test.rst

+6
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,12 @@ The :mod:`test.support` module defines the following constants:
367367
Object that is equal to anything. Used to test mixed type comparison.
368368

369369

370+
.. data:: NEVER_EQ
371+
372+
Object that is not equal to anything (even to :data:`ALWAYS_EQ`).
373+
Used to test mixed type comparison.
374+
375+
370376
.. data:: LARGEST
371377

372378
Object that is greater than anything (except itself).

Lib/test/list_tests.py

+15
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from functools import cmp_to_key
88

99
from test import support, seq_tests
10+
from test.support import ALWAYS_EQ, NEVER_EQ
1011

1112

1213
class CommonTest(seq_tests.CommonTest):
@@ -329,6 +330,20 @@ def test_remove(self):
329330

330331
self.assertRaises(TypeError, a.remove)
331332

333+
a = self.type2test([1, 2])
334+
self.assertRaises(ValueError, a.remove, NEVER_EQ)
335+
self.assertEqual(a, [1, 2])
336+
a.remove(ALWAYS_EQ)
337+
self.assertEqual(a, [2])
338+
a = self.type2test([ALWAYS_EQ])
339+
a.remove(1)
340+
self.assertEqual(a, [])
341+
a = self.type2test([ALWAYS_EQ])
342+
a.remove(NEVER_EQ)
343+
self.assertEqual(a, [])
344+
a = self.type2test([NEVER_EQ])
345+
self.assertRaises(ValueError, a.remove, ALWAYS_EQ)
346+
332347
class BadExc(Exception):
333348
pass
334349

Lib/test/seq_tests.py

+20-9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import sys
77
import pickle
88
from test import support
9+
from test.support import ALWAYS_EQ, NEVER_EQ
910

1011
# Various iterables
1112
# This is used for checking the constructor (here and in test_deque.py)
@@ -221,15 +222,15 @@ def test_contains(self):
221222
self.assertRaises(TypeError, u.__contains__)
222223

223224
def test_contains_fake(self):
224-
class AllEq:
225-
# Sequences must use rich comparison against each item
226-
# (unless "is" is true, or an earlier item answered)
227-
# So instances of AllEq must be found in all non-empty sequences.
228-
def __eq__(self, other):
229-
return True
230-
__hash__ = None # Can't meet hash invariant requirements
231-
self.assertNotIn(AllEq(), self.type2test([]))
232-
self.assertIn(AllEq(), self.type2test([1]))
225+
# Sequences must use rich comparison against each item
226+
# (unless "is" is true, or an earlier item answered)
227+
# So ALWAYS_EQ must be found in all non-empty sequences.
228+
self.assertNotIn(ALWAYS_EQ, self.type2test([]))
229+
self.assertIn(ALWAYS_EQ, self.type2test([1]))
230+
self.assertIn(1, self.type2test([ALWAYS_EQ]))
231+
self.assertNotIn(NEVER_EQ, self.type2test([]))
232+
self.assertNotIn(ALWAYS_EQ, self.type2test([NEVER_EQ]))
233+
self.assertIn(NEVER_EQ, self.type2test([ALWAYS_EQ]))
233234

234235
def test_contains_order(self):
235236
# Sequences must test in-order. If a rich comparison has side
@@ -350,6 +351,11 @@ def test_count(self):
350351
self.assertEqual(a.count(1), 3)
351352
self.assertEqual(a.count(3), 0)
352353

354+
self.assertEqual(a.count(ALWAYS_EQ), 9)
355+
self.assertEqual(self.type2test([ALWAYS_EQ, ALWAYS_EQ]).count(1), 2)
356+
self.assertEqual(self.type2test([ALWAYS_EQ, ALWAYS_EQ]).count(NEVER_EQ), 2)
357+
self.assertEqual(self.type2test([NEVER_EQ, NEVER_EQ]).count(ALWAYS_EQ), 0)
358+
353359
self.assertRaises(TypeError, a.count)
354360

355361
class BadExc(Exception):
@@ -378,6 +384,11 @@ def test_index(self):
378384
self.assertEqual(u.index(0, 3, 4), 3)
379385
self.assertRaises(ValueError, u.index, 2, 0, -10)
380386

387+
self.assertEqual(u.index(ALWAYS_EQ), 0)
388+
self.assertEqual(self.type2test([ALWAYS_EQ, ALWAYS_EQ]).index(1), 0)
389+
self.assertEqual(self.type2test([ALWAYS_EQ, ALWAYS_EQ]).index(NEVER_EQ), 0)
390+
self.assertRaises(ValueError, self.type2test([NEVER_EQ, NEVER_EQ]).index, ALWAYS_EQ)
391+
381392
self.assertRaises(TypeError, u.index)
382393

383394
class BadExc(Exception):

Lib/test/support/__init__.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
"run_with_locale", "swap_item",
114114
"swap_attr", "Matcher", "set_memlimit", "SuppressCrashReport", "sortdict",
115115
"run_with_tz", "PGO", "missing_compiler_executable", "fd_count",
116-
"ALWAYS_EQ", "LARGEST", "SMALLEST"
116+
"ALWAYS_EQ", "NEVER_EQ", "LARGEST", "SMALLEST"
117117
]
118118

119119
class Error(Exception):
@@ -3115,6 +3115,17 @@ def __ne__(self, other):
31153115

31163116
ALWAYS_EQ = _ALWAYS_EQ()
31173117

3118+
class _NEVER_EQ:
3119+
"""
3120+
Object that is not equal to anything.
3121+
"""
3122+
def __eq__(self, other):
3123+
return False
3124+
def __ne__(self, other):
3125+
return True
3126+
3127+
NEVER_EQ = _NEVER_EQ()
3128+
31183129
@functools.total_ordering
31193130
class _LARGEST:
31203131
"""

Lib/test/test_iter.py

+22-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
import unittest
55
from test.support import run_unittest, TESTFN, unlink, cpython_only
6-
from test.support import check_free_after_iterating
6+
from test.support import check_free_after_iterating, ALWAYS_EQ, NEVER_EQ
77
import pickle
88
import collections.abc
99

@@ -41,6 +41,14 @@ def __init__(self, n):
4141
def __iter__(self):
4242
return BasicIterClass(self.n)
4343

44+
class IteratorProxyClass:
45+
def __init__(self, i):
46+
self.i = i
47+
def __next__(self):
48+
return next(self.i)
49+
def __iter__(self):
50+
return self
51+
4452
class SequenceClass:
4553
def __init__(self, n):
4654
self.n = n
@@ -50,6 +58,12 @@ def __getitem__(self, i):
5058
else:
5159
raise IndexError
5260

61+
class SequenceProxyClass:
62+
def __init__(self, s):
63+
self.s = s
64+
def __getitem__(self, i):
65+
return self.s[i]
66+
5367
class UnlimitedSequenceClass:
5468
def __getitem__(self, i):
5569
return i
@@ -635,6 +649,13 @@ def test_in_and_not_in(self):
635649
for i in "abc", -1, 5, 42.42, (3, 4), [], {1: 1}, 3-12j, sc5:
636650
self.assertNotIn(i, sc5)
637651

652+
self.assertIn(ALWAYS_EQ, IteratorProxyClass(iter([1])))
653+
self.assertIn(ALWAYS_EQ, SequenceProxyClass([1]))
654+
self.assertNotIn(ALWAYS_EQ, IteratorProxyClass(iter([NEVER_EQ])))
655+
self.assertNotIn(ALWAYS_EQ, SequenceProxyClass([NEVER_EQ]))
656+
self.assertIn(NEVER_EQ, IteratorProxyClass(iter([ALWAYS_EQ])))
657+
self.assertIn(NEVER_EQ, SequenceProxyClass([ALWAYS_EQ]))
658+
638659
self.assertRaises(TypeError, lambda: 3 in 12)
639660
self.assertRaises(TypeError, lambda: 3 not in map)
640661

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed minor inconsistency in :meth:`list.__contains__`,
2+
:meth:`tuple.__contains__` and a few other places. The collection's item is
3+
now always at the left and the needle is on the right of ``==``.

Modules/_asynciomodule.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,7 @@ _asyncio_Future_remove_done_callback(FutureObj *self, PyObject *fn)
937937
ENSURE_FUTURE_ALIVE(self)
938938

939939
if (self->fut_callback0 != NULL) {
940-
int cmp = PyObject_RichCompareBool(fn, self->fut_callback0, Py_EQ);
940+
int cmp = PyObject_RichCompareBool(self->fut_callback0, fn, Py_EQ);
941941
if (cmp == -1) {
942942
return NULL;
943943
}
@@ -962,7 +962,7 @@ _asyncio_Future_remove_done_callback(FutureObj *self, PyObject *fn)
962962
if (len == 1) {
963963
PyObject *cb_tup = PyList_GET_ITEM(self->fut_callbacks, 0);
964964
int cmp = PyObject_RichCompareBool(
965-
fn, PyTuple_GET_ITEM(cb_tup, 0), Py_EQ);
965+
PyTuple_GET_ITEM(cb_tup, 0), fn, Py_EQ);
966966
if (cmp == -1) {
967967
return NULL;
968968
}
@@ -984,7 +984,7 @@ _asyncio_Future_remove_done_callback(FutureObj *self, PyObject *fn)
984984
int ret;
985985
PyObject *item = PyList_GET_ITEM(self->fut_callbacks, i);
986986
Py_INCREF(item);
987-
ret = PyObject_RichCompareBool(fn, PyTuple_GET_ITEM(item, 0), Py_EQ);
987+
ret = PyObject_RichCompareBool(PyTuple_GET_ITEM(item, 0), fn, Py_EQ);
988988
if (ret == 0) {
989989
if (j < len) {
990990
PyList_SET_ITEM(newlist, j, item);

Modules/_ssl.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -5600,8 +5600,7 @@ list_contains(PyListObject *a, PyObject *el)
56005600
int cmp;
56015601

56025602
for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i)
5603-
cmp = PyObject_RichCompareBool(el, PyList_GET_ITEM(a, i),
5604-
Py_EQ);
5603+
cmp = PyObject_RichCompareBool(PyList_GET_ITEM(a, i), el, Py_EQ);
56055604
return cmp;
56065605
}
56075606

Objects/abstract.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -2016,7 +2016,7 @@ _PySequence_IterSearch(PyObject *seq, PyObject *obj, int operation)
20162016
break;
20172017
}
20182018

2019-
cmp = PyObject_RichCompareBool(obj, item, Py_EQ);
2019+
cmp = PyObject_RichCompareBool(item, obj, Py_EQ);
20202020
Py_DECREF(item);
20212021
if (cmp < 0)
20222022
goto Fail;

Objects/dictobject.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -4392,7 +4392,7 @@ dictitems_contains(_PyDictViewObject *dv, PyObject *obj)
43924392
return 0;
43934393
}
43944394
Py_INCREF(found);
4395-
result = PyObject_RichCompareBool(value, found, Py_EQ);
4395+
result = PyObject_RichCompareBool(found, value, Py_EQ);
43964396
Py_DECREF(found);
43974397
return result;
43984398
}

Objects/listobject.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,7 @@ list_contains(PyListObject *a, PyObject *el)
449449
int cmp;
450450

451451
for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i)
452-
cmp = PyObject_RichCompareBool(el, PyList_GET_ITEM(a, i),
453-
Py_EQ);
452+
cmp = PyObject_RichCompareBool(PyList_GET_ITEM(a, i), el, Py_EQ);
454453
return cmp;
455454
}
456455

Objects/tupleobject.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -403,8 +403,7 @@ tuplecontains(PyTupleObject *a, PyObject *el)
403403
int cmp;
404404

405405
for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i)
406-
cmp = PyObject_RichCompareBool(el, PyTuple_GET_ITEM(a, i),
407-
Py_EQ);
406+
cmp = PyObject_RichCompareBool(PyTuple_GET_ITEM(a, i), el, Py_EQ);
408407
return cmp;
409408
}
410409

0 commit comments

Comments
 (0)