Make 'x in y' and 'x not in y' (PySequence_Contains) play nice w/ iterators.

NEEDS DOC CHANGES
A few more AttributeErrors turned into TypeErrors, but in test_contains
this time.
The full story for instance objects is pretty much unexplainable, because
instance_contains() tries its own flavor of iteration-based containment
testing first, and PySequence_Contains doesn't get a chance at it unless
instance_contains() blows up.  A consequence is that
    some_complex_number in some_instance
dies with a TypeError unless some_instance.__class__ defines __iter__ but
does not define __getitem__.
This commit is contained in:
Tim Peters 2001-05-05 10:06:17 +00:00
parent 2cfe368283
commit de9725f135
5 changed files with 93 additions and 32 deletions

View File

@ -31,13 +31,13 @@ check(0 not in c, "0 in seq(1)")
try: try:
1 in a 1 in a
check(0, "in base_set did not raise error") check(0, "in base_set did not raise error")
except AttributeError: except TypeError:
pass pass
try: try:
1 not in a 1 not in a
check(0, "not in base_set did not raise error") check(0, "not in base_set did not raise error")
except AttributeError: except TypeError:
pass pass
# Test char in string # Test char in string

View File

@ -472,4 +472,59 @@ class TestCase(unittest.TestCase):
except OSError: except OSError:
pass pass
# Test iterators with 'x in y' and 'x not in y'.
def test_in_and_not_in(self):
sc5 = IteratingSequenceClass(5)
for i in range(5):
self.assert_(i in sc5)
# CAUTION: This test fails on 3-12j if sc5 is SequenceClass(5)
# instead, with:
# TypeError: cannot compare complex numbers using <, <=, >, >=
# 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 not in map)
d = {"one": 1, "two": 2, "three": 3, 1j: 2j}
for k in d:
self.assert_(k in d)
self.assert_(k not in d.itervalues())
for v in d.values():
self.assert_(v in d.itervalues())
self.assert_(v not in d)
for k, v in d.iteritems():
self.assert_((k, v) in d.iteritems())
self.assert_((v, k) not in d.iteritems())
del d
f = open(TESTFN, "w")
try:
f.write("a\n" "b\n" "c\n")
finally:
f.close()
f = open(TESTFN, "r")
try:
for chunk in "abc":
f.seek(0, 0)
self.assert_(chunk not in f)
f.seek(0, 0)
self.assert_((chunk + "\n") in f)
finally:
f.close()
try:
unlink(TESTFN)
except OSError:
pass
run_unittest(TestCase) run_unittest(TestCase)

View File

@ -23,11 +23,11 @@ Core
max() max()
min() min()
reduce() reduce()
string.join() .join() method of strings
tuple() tuple()
unicode.join() unicode.join()
XXX TODO zip() XXX TODO zip()
XXX TODO 'x in y' 'x in y' and 'x not in y'
What's New in Python 2.1 (final)? What's New in Python 2.1 (final)?
================================= =================================

View File

@ -1363,46 +1363,51 @@ PySequence_Count(PyObject *s, PyObject *o)
return n; return n;
} }
/* Return -1 if error; 1 if v in w; 0 if v not in w. */
int int
PySequence_Contains(PyObject *w, PyObject *v) /* v in w */ PySequence_Contains(PyObject *w, PyObject *v) /* v in w */
{ {
int i, cmp; PyObject *it; /* iter(w) */
PyObject *x; int result;
PySequenceMethods *sq;
if(PyType_HasFeature(w->ob_type, Py_TPFLAGS_HAVE_SEQUENCE_IN)) { if (PyType_HasFeature(w->ob_type, Py_TPFLAGS_HAVE_SEQUENCE_IN)) {
sq = w->ob_type->tp_as_sequence; PySequenceMethods *sq = w->ob_type->tp_as_sequence;
if(sq != NULL && sq->sq_contains != NULL) if (sq != NULL && sq->sq_contains != NULL) {
return (*sq->sq_contains)(w, v); result = (*sq->sq_contains)(w, v);
if (result >= 0)
return result;
assert(PyErr_Occurred());
if (PyErr_ExceptionMatches(PyExc_AttributeError))
PyErr_Clear();
else
return result;
}
} }
/* If there is no better way to check whether an item is is contained, /* Try exhaustive iteration. */
do it the hard way */ it = PyObject_GetIter(w);
sq = w->ob_type->tp_as_sequence; if (it == NULL) {
if (sq == NULL || sq->sq_item == NULL) {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
"'in' or 'not in' needs sequence right argument"); "'in' or 'not in' needs iterable right argument");
return -1; return -1;
} }
for (i = 0; ; i++) { for (;;) {
x = (*sq->sq_item)(w, i); int cmp;
if (x == NULL) { PyObject *item = PyIter_Next(it);
if (PyErr_ExceptionMatches(PyExc_IndexError)) { if (item == NULL) {
PyErr_Clear(); result = PyErr_Occurred() ? -1 : 0;
break; break;
}
return -1;
} }
cmp = PyObject_RichCompareBool(v, x, Py_EQ); cmp = PyObject_RichCompareBool(v, item, Py_EQ);
Py_XDECREF(x); Py_DECREF(item);
if (cmp > 0) if (cmp == 0)
return 1; continue;
if (cmp < 0) result = cmp > 0 ? 1 : -1;
return -1; break;
} }
Py_DECREF(it);
return 0; return result;
} }
/* Backwards compatibility */ /* Backwards compatibility */

View File

@ -835,6 +835,7 @@ PyObject_RichCompare(PyObject *v, PyObject *w, int op)
return res; return res;
} }
/* Return -1 if error; 1 if v op w; 0 if not (v op w). */
int int
PyObject_RichCompareBool(PyObject *v, PyObject *w, int op) PyObject_RichCompareBool(PyObject *v, PyObject *w, int op)
{ {