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:
parent
a8defaae04
commit
cb8d368b82
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue