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.
This commit is contained in:
Tim Peters 2001-05-05 21:05:01 +00:00
parent a8defaae04
commit cb8d368b82
4 changed files with 67 additions and 77 deletions

View File

@ -932,7 +932,17 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
expression: o.count(value). expression: o.count(value).
*/ */
DL_IMPORT(int) PySequence_Contains(PyObject *o, PyObject *value); DL_IMPORT(int) PySequence_Contains(PyObject *seq, PyObject *ob);
/*
Return -1 if error; 1 if ob in seq; 0 if ob not in seq.
Use __contains__ if possible, else _PySequence_IterContains().
*/
DL_IMPORT(int) _PySequence_IterContains(PyObject *seq, PyObject *ob);
/*
Return -1 if error; 1 if ob in seq; 0 if ob not in seq.
Always uses the iteration protocol, and only Py_EQ comparisons.
*/
/* For DLL-level backwards compatibility */ /* For DLL-level backwards compatibility */
#undef PySequence_In #undef PySequence_In

View File

@ -474,24 +474,12 @@ class TestCase(unittest.TestCase):
# Test iterators with 'x in y' and 'x not in y'. # Test iterators with 'x in y' and 'x not in y'.
def test_in_and_not_in(self): def test_in_and_not_in(self):
sc5 = IteratingSequenceClass(5) for sc5 in IteratingSequenceClass(5), SequenceClass(5):
for i in range(5): for i in range(5):
self.assert_(i in sc5) self.assert_(i in sc5)
# CAUTION: This test fails on 3-12j if sc5 is SequenceClass(5) for i in "abc", -1, 5, 42.42, (3, 4), [], {1: 1}, 3-12j, sc5:
# instead, with: self.assert_(i not in sc5)
# TypeError: cannot compare complex numbers using <, <=, >, >= del sc5
# The trail leads back to instance_contains() in classobject.c,
# under comment:
# /* fall back to previous behavior */
# IteratingSequenceClass(5) avoids the same problem only because
# it lacks __getitem__: instance_contains *tries* to do a wrong
# thing with it too, but aborts with an AttributeError the first
# time it calls instance_item(); PySequence_Contains() then catches
# that and clears it, and tries the iterator-based "contains"
# instead. But this is hanging together by a thread.
for i in "abc", -1, 5, 42.42, (3, 4), [], {1: 1}, 3-12j, sc5:
self.assert_(i not in sc5)
del sc5
self.assertRaises(TypeError, lambda: 3 in 12) self.assertRaises(TypeError, lambda: 3 in 12)
self.assertRaises(TypeError, lambda: 3 not in map) self.assertRaises(TypeError, lambda: 3 not in map)

View File

@ -1381,29 +1381,14 @@ Fail:
return -1; return -1;
} }
/* Return -1 if error; 1 if v in w; 0 if v not in w. */ /* Return -1 if error; 1 if ob in seq; 0 if ob not in seq.
* Always uses the iteration protocol, and only Py_EQ comparison.
*/
int int
PySequence_Contains(PyObject *w, PyObject *v) /* v in w */ _PySequence_IterContains(PyObject *seq, PyObject *ob)
{ {
PyObject *it; /* iter(w) */
int result; int result;
PyObject *it = PyObject_GetIter(seq);
if (PyType_HasFeature(w->ob_type, Py_TPFLAGS_HAVE_SEQUENCE_IN)) {
PySequenceMethods *sq = w->ob_type->tp_as_sequence;
if (sq != NULL && sq->sq_contains != NULL) {
result = (*sq->sq_contains)(w, v);
if (result >= 0)
return result;
assert(PyErr_Occurred());
if (PyErr_ExceptionMatches(PyExc_AttributeError))
PyErr_Clear();
else
return result;
}
}
/* Try exhaustive iteration. */
it = PyObject_GetIter(w);
if (it == NULL) { if (it == NULL) {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
"'in' or 'not in' needs iterable right argument"); "'in' or 'not in' needs iterable right argument");
@ -1417,7 +1402,7 @@ PySequence_Contains(PyObject *w, PyObject *v) /* v in w */
result = PyErr_Occurred() ? -1 : 0; result = PyErr_Occurred() ? -1 : 0;
break; break;
} }
cmp = PyObject_RichCompareBool(v, item, Py_EQ); cmp = PyObject_RichCompareBool(ob, item, Py_EQ);
Py_DECREF(item); Py_DECREF(item);
if (cmp == 0) if (cmp == 0)
continue; continue;
@ -1428,6 +1413,20 @@ PySequence_Contains(PyObject *w, PyObject *v) /* v in w */
return result; return result;
} }
/* Return -1 if error; 1 if ob in seq; 0 if ob not in seq.
* Use sq_contains if possible, else defer to _PySequence_IterContains().
*/
int
PySequence_Contains(PyObject *seq, PyObject *ob)
{
if (PyType_HasFeature(seq->ob_type, Py_TPFLAGS_HAVE_SEQUENCE_IN)) {
PySequenceMethods *sqm = seq->ob_type->tp_as_sequence;
if (sqm != NULL && sqm->sq_contains != NULL)
return (*sqm->sq_contains)(seq, ob);
}
return _PySequence_IterContains(seq, ob);
}
/* Backwards compatibility */ /* Backwards compatibility */
#undef PySequence_In #undef PySequence_In
int int

View File

@ -1131,11 +1131,15 @@ instance_ass_slice(PyInstanceObject *inst, int i, int j, PyObject *value)
return 0; return 0;
} }
static int instance_contains(PyInstanceObject *inst, PyObject *member) static int
instance_contains(PyInstanceObject *inst, PyObject *member)
{ {
static PyObject *__contains__; static PyObject *__contains__;
PyObject *func, *arg, *res; PyObject *func;
int ret;
/* Try __contains__ first.
* If that can't be done, try iterator-based searching.
*/
if(__contains__ == NULL) { if(__contains__ == NULL) {
__contains__ = PyString_InternFromString("__contains__"); __contains__ = PyString_InternFromString("__contains__");
@ -1143,45 +1147,34 @@ static int instance_contains(PyInstanceObject *inst, PyObject *member)
return -1; return -1;
} }
func = instance_getattr(inst, __contains__); func = instance_getattr(inst, __contains__);
if(func == NULL) { if (func) {
/* fall back to previous behavior */ PyObject *res;
int i, cmp_res; int ret;
PyObject *arg = Py_BuildValue("(O)", member);
if(!PyErr_ExceptionMatches(PyExc_AttributeError)) if(arg == NULL) {
Py_DECREF(func);
return -1; return -1;
PyErr_Clear();
for(i=0;;i++) {
PyObject *obj = instance_item(inst, i);
int ret = 0;
if(obj == NULL) {
if(!PyErr_ExceptionMatches(PyExc_IndexError))
return -1;
PyErr_Clear();
return 0;
}
if(PyObject_Cmp(obj, member, &cmp_res) == -1)
ret = -1;
if(cmp_res == 0)
ret = 1;
Py_DECREF(obj);
if(ret)
return ret;
} }
} res = PyEval_CallObject(func, arg);
arg = Py_BuildValue("(O)", member);
if(arg == NULL) {
Py_DECREF(func); Py_DECREF(func);
return -1; Py_DECREF(arg);
if(res == NULL)
return -1;
ret = PyObject_IsTrue(res);
Py_DECREF(res);
return ret;
} }
res = PyEval_CallObject(func, arg);
Py_DECREF(func); /* Couldn't find __contains__. */
Py_DECREF(arg); if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
if(res == NULL) /* Assume the failure was simply due to that there is no
* __contains__ attribute, and try iterating instead.
*/
PyErr_Clear();
return _PySequence_IterContains((PyObject *)inst, member);
}
else
return -1; return -1;
ret = PyObject_IsTrue(res);
Py_DECREF(res);
return ret;
} }
static PySequenceMethods static PySequenceMethods