Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cb8d368

Browse files
committed
Reimplement PySequence_Contains() and instance_contains(), so they work
safely together and don't duplicate logic (the common logic was factored out into new private API function _PySequence_IterContains()). Visible change: some_complex_number in some_instance no longer blows up if some_instance has __getitem__ but neither __contains__ nor __iter__. test_iter changed to ensure that remains true.
1 parent a8defaa commit cb8d368

4 files changed

Lines changed: 67 additions & 77 deletions

File tree

Include/abstract.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,17 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
932932
expression: o.count(value).
933933
*/
934934

935-
DL_IMPORT(int) PySequence_Contains(PyObject *o, PyObject *value);
935+
DL_IMPORT(int) PySequence_Contains(PyObject *seq, PyObject *ob);
936+
/*
937+
Return -1 if error; 1 if ob in seq; 0 if ob not in seq.
938+
Use __contains__ if possible, else _PySequence_IterContains().
939+
*/
940+
941+
DL_IMPORT(int) _PySequence_IterContains(PyObject *seq, PyObject *ob);
942+
/*
943+
Return -1 if error; 1 if ob in seq; 0 if ob not in seq.
944+
Always uses the iteration protocol, and only Py_EQ comparisons.
945+
*/
936946

937947
/* For DLL-level backwards compatibility */
938948
#undef PySequence_In

Lib/test/test_iter.py

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -474,24 +474,12 @@ def next(self):
474474

475475
# Test iterators with 'x in y' and 'x not in y'.
476476
def test_in_and_not_in(self):
477-
sc5 = IteratingSequenceClass(5)
478-
for i in range(5):
479-
self.assert_(i in sc5)
480-
# CAUTION: This test fails on 3-12j if sc5 is SequenceClass(5)
481-
# instead, with:
482-
# TypeError: cannot compare complex numbers using <, <=, >, >=
483-
# The trail leads back to instance_contains() in classobject.c,
484-
# under comment:
485-
# /* fall back to previous behavior */
486-
# IteratingSequenceClass(5) avoids the same problem only because
487-
# it lacks __getitem__: instance_contains *tries* to do a wrong
488-
# thing with it too, but aborts with an AttributeError the first
489-
# time it calls instance_item(); PySequence_Contains() then catches
490-
# that and clears it, and tries the iterator-based "contains"
491-
# instead. But this is hanging together by a thread.
492-
for i in "abc", -1, 5, 42.42, (3, 4), [], {1: 1}, 3-12j, sc5:
493-
self.assert_(i not in sc5)
494-
del sc5
477+
for sc5 in IteratingSequenceClass(5), SequenceClass(5):
478+
for i in range(5):
479+
self.assert_(i in sc5)
480+
for i in "abc", -1, 5, 42.42, (3, 4), [], {1: 1}, 3-12j, sc5:
481+
self.assert_(i not in sc5)
482+
del sc5
495483

496484
self.assertRaises(TypeError, lambda: 3 in 12)
497485
self.assertRaises(TypeError, lambda: 3 not in map)

Objects/abstract.c

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,29 +1381,14 @@ PySequence_Count(PyObject *s, PyObject *o)
13811381
return -1;
13821382
}
13831383

1384-
/* Return -1 if error; 1 if v in w; 0 if v not in w. */
1384+
/* Return -1 if error; 1 if ob in seq; 0 if ob not in seq.
1385+
* Always uses the iteration protocol, and only Py_EQ comparison.
1386+
*/
13851387
int
1386-
PySequence_Contains(PyObject *w, PyObject *v) /* v in w */
1388+
_PySequence_IterContains(PyObject *seq, PyObject *ob)
13871389
{
1388-
PyObject *it; /* iter(w) */
13891390
int result;
1390-
1391-
if (PyType_HasFeature(w->ob_type, Py_TPFLAGS_HAVE_SEQUENCE_IN)) {
1392-
PySequenceMethods *sq = w->ob_type->tp_as_sequence;
1393-
if (sq != NULL && sq->sq_contains != NULL) {
1394-
result = (*sq->sq_contains)(w, v);
1395-
if (result >= 0)
1396-
return result;
1397-
assert(PyErr_Occurred());
1398-
if (PyErr_ExceptionMatches(PyExc_AttributeError))
1399-
PyErr_Clear();
1400-
else
1401-
return result;
1402-
}
1403-
}
1404-
1405-
/* Try exhaustive iteration. */
1406-
it = PyObject_GetIter(w);
1391+
PyObject *it = PyObject_GetIter(seq);
14071392
if (it == NULL) {
14081393
PyErr_SetString(PyExc_TypeError,
14091394
"'in' or 'not in' needs iterable right argument");
@@ -1417,7 +1402,7 @@ PySequence_Contains(PyObject *w, PyObject *v) /* v in w */
14171402
result = PyErr_Occurred() ? -1 : 0;
14181403
break;
14191404
}
1420-
cmp = PyObject_RichCompareBool(v, item, Py_EQ);
1405+
cmp = PyObject_RichCompareBool(ob, item, Py_EQ);
14211406
Py_DECREF(item);
14221407
if (cmp == 0)
14231408
continue;
@@ -1428,6 +1413,20 @@ PySequence_Contains(PyObject *w, PyObject *v) /* v in w */
14281413
return result;
14291414
}
14301415

1416+
/* Return -1 if error; 1 if ob in seq; 0 if ob not in seq.
1417+
* Use sq_contains if possible, else defer to _PySequence_IterContains().
1418+
*/
1419+
int
1420+
PySequence_Contains(PyObject *seq, PyObject *ob)
1421+
{
1422+
if (PyType_HasFeature(seq->ob_type, Py_TPFLAGS_HAVE_SEQUENCE_IN)) {
1423+
PySequenceMethods *sqm = seq->ob_type->tp_as_sequence;
1424+
if (sqm != NULL && sqm->sq_contains != NULL)
1425+
return (*sqm->sq_contains)(seq, ob);
1426+
}
1427+
return _PySequence_IterContains(seq, ob);
1428+
}
1429+
14311430
/* Backwards compatibility */
14321431
#undef PySequence_In
14331432
int

Objects/classobject.c

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,57 +1131,50 @@ instance_ass_slice(PyInstanceObject *inst, int i, int j, PyObject *value)
11311131
return 0;
11321132
}
11331133

1134-
static int instance_contains(PyInstanceObject *inst, PyObject *member)
1134+
static int
1135+
instance_contains(PyInstanceObject *inst, PyObject *member)
11351136
{
11361137
static PyObject *__contains__;
1137-
PyObject *func, *arg, *res;
1138-
int ret;
1138+
PyObject *func;
1139+
1140+
/* Try __contains__ first.
1141+
* If that can't be done, try iterator-based searching.
1142+
*/
11391143

11401144
if(__contains__ == NULL) {
11411145
__contains__ = PyString_InternFromString("__contains__");
11421146
if(__contains__ == NULL)
11431147
return -1;
11441148
}
11451149
func = instance_getattr(inst, __contains__);
1146-
if(func == NULL) {
1147-
/* fall back to previous behavior */
1148-
int i, cmp_res;
1149-
1150-
if(!PyErr_ExceptionMatches(PyExc_AttributeError))
1150+
if (func) {
1151+
PyObject *res;
1152+
int ret;
1153+
PyObject *arg = Py_BuildValue("(O)", member);
1154+
if(arg == NULL) {
1155+
Py_DECREF(func);
11511156
return -1;
1152-
PyErr_Clear();
1153-
for(i=0;;i++) {
1154-
PyObject *obj = instance_item(inst, i);
1155-
int ret = 0;
1156-
1157-
if(obj == NULL) {
1158-
if(!PyErr_ExceptionMatches(PyExc_IndexError))
1159-
return -1;
1160-
PyErr_Clear();
1161-
return 0;
1162-
}
1163-
if(PyObject_Cmp(obj, member, &cmp_res) == -1)
1164-
ret = -1;
1165-
if(cmp_res == 0)
1166-
ret = 1;
1167-
Py_DECREF(obj);
1168-
if(ret)
1169-
return ret;
11701157
}
1171-
}
1172-
arg = Py_BuildValue("(O)", member);
1173-
if(arg == NULL) {
1158+
res = PyEval_CallObject(func, arg);
11741159
Py_DECREF(func);
1175-
return -1;
1160+
Py_DECREF(arg);
1161+
if(res == NULL)
1162+
return -1;
1163+
ret = PyObject_IsTrue(res);
1164+
Py_DECREF(res);
1165+
return ret;
11761166
}
1177-
res = PyEval_CallObject(func, arg);
1178-
Py_DECREF(func);
1179-
Py_DECREF(arg);
1180-
if(res == NULL)
1167+
1168+
/* Couldn't find __contains__. */
1169+
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
1170+
/* Assume the failure was simply due to that there is no
1171+
* __contains__ attribute, and try iterating instead.
1172+
*/
1173+
PyErr_Clear();
1174+
return _PySequence_IterContains((PyObject *)inst, member);
1175+
}
1176+
else
11811177
return -1;
1182-
ret = PyObject_IsTrue(res);
1183-
Py_DECREF(res);
1184-
return ret;
11851178
}
11861179

11871180
static PySequenceMethods

0 commit comments

Comments
 (0)