summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Peters <tim.peters@gmail.com>2001-05-05 10:06:17 (GMT)
committerTim Peters <tim.peters@gmail.com>2001-05-05 10:06:17 (GMT)
commitde9725f1352856b7d6de1bb29383a7be5f181740 (patch)
tree3fff3245c5dff4684440f7e9227c34bed9ef27b5
parent2cfe36828342e16cd274b968736a01aed5c49557 (diff)
downloadcpython-de9725f1352856b7d6de1bb29383a7be5f181740.zip
cpython-de9725f1352856b7d6de1bb29383a7be5f181740.tar.gz
cpython-de9725f1352856b7d6de1bb29383a7be5f181740.tar.bz2
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__.
-rw-r--r--Lib/test/test_contains.py4
-rw-r--r--Lib/test/test_iter.py55
-rw-r--r--Misc/NEWS4
-rw-r--r--Objects/abstract.c63
-rw-r--r--Objects/object.c1
5 files changed, 94 insertions, 33 deletions
diff --git a/Lib/test/test_contains.py b/Lib/test/test_contains.py
index 499d587..8fec425 100644
--- a/Lib/test/test_contains.py
+++ b/Lib/test/test_contains.py
@@ -31,13 +31,13 @@ check(0 not in c, "0 in seq(1)")
try:
1 in a
check(0, "in base_set did not raise error")
-except AttributeError:
+except TypeError:
pass
try:
1 not in a
check(0, "not in base_set did not raise error")
-except AttributeError:
+except TypeError:
pass
# Test char in string
diff --git a/Lib/test/test_iter.py b/Lib/test/test_iter.py
index 073ffb4..bb9b102 100644
--- a/Lib/test/test_iter.py
+++ b/Lib/test/test_iter.py
@@ -472,4 +472,59 @@ class TestCase(unittest.TestCase):
except OSError:
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)
diff --git a/Misc/NEWS b/Misc/NEWS
index d556afa..469d21f 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -23,11 +23,11 @@ Core
max()
min()
reduce()
- string.join()
+ .join() method of strings
tuple()
unicode.join()
XXX TODO zip()
- XXX TODO 'x in y'
+ 'x in y' and 'x not in y'
What's New in Python 2.1 (final)?
=================================
diff --git a/Objects/abstract.c b/Objects/abstract.c
index 30e6191..a0a40e8 100644
--- a/Objects/abstract.c
+++ b/Objects/abstract.c
@@ -1363,46 +1363,51 @@ PySequence_Count(PyObject *s, PyObject *o)
return n;
}
+/* Return -1 if error; 1 if v in w; 0 if v not in w. */
int
PySequence_Contains(PyObject *w, PyObject *v) /* v in w */
{
- int i, cmp;
- PyObject *x;
- PySequenceMethods *sq;
-
- if(PyType_HasFeature(w->ob_type, Py_TPFLAGS_HAVE_SEQUENCE_IN)) {
- sq = w->ob_type->tp_as_sequence;
- if(sq != NULL && sq->sq_contains != NULL)
- return (*sq->sq_contains)(w, v);
+ PyObject *it; /* iter(w) */
+ int result;
+
+ 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;
+ }
}
- /* If there is no better way to check whether an item is is contained,
- do it the hard way */
- sq = w->ob_type->tp_as_sequence;
- if (sq == NULL || sq->sq_item == NULL) {
+ /* Try exhaustive iteration. */
+ it = PyObject_GetIter(w);
+ if (it == NULL) {
PyErr_SetString(PyExc_TypeError,
- "'in' or 'not in' needs sequence right argument");
+ "'in' or 'not in' needs iterable right argument");
return -1;
}
- for (i = 0; ; i++) {
- x = (*sq->sq_item)(w, i);
- if (x == NULL) {
- if (PyErr_ExceptionMatches(PyExc_IndexError)) {
- PyErr_Clear();
- break;
- }
- return -1;
+ for (;;) {
+ int cmp;
+ PyObject *item = PyIter_Next(it);
+ if (item == NULL) {
+ result = PyErr_Occurred() ? -1 : 0;
+ break;
}
- cmp = PyObject_RichCompareBool(v, x, Py_EQ);
- Py_XDECREF(x);
- if (cmp > 0)
- return 1;
- if (cmp < 0)
- return -1;
+ cmp = PyObject_RichCompareBool(v, item, Py_EQ);
+ Py_DECREF(item);
+ if (cmp == 0)
+ continue;
+ result = cmp > 0 ? 1 : -1;
+ break;
}
-
- return 0;
+ Py_DECREF(it);
+ return result;
}
/* Backwards compatibility */
diff --git a/Objects/object.c b/Objects/object.c
index 2c033f8..f952405 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -835,6 +835,7 @@ PyObject_RichCompare(PyObject *v, PyObject *w, int op)
return res;
}
+/* Return -1 if error; 1 if v op w; 0 if not (v op w). */
int
PyObject_RichCompareBool(PyObject *v, PyObject *w, int op)
{