]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Make 'x in y' and 'x not in y' (PySequence_Contains) play nice w/ iterators.
authorTim Peters <tim.peters@gmail.com>
Sat, 5 May 2001 10:06:17 +0000 (10:06 +0000)
committerTim Peters <tim.peters@gmail.com>
Sat, 5 May 2001 10:06:17 +0000 (10:06 +0000)
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__.

Lib/test/test_contains.py
Lib/test/test_iter.py
Misc/NEWS
Objects/abstract.c
Objects/object.c

index 499d587a0ac81ce726f854e93593facf14dae349..8fec4251b1ec9ce6a31a131c3b9e287d388dfacc 100644 (file)
@@ -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
index 073ffb452e8826a4288904871524f5adcd188af8..bb9b102c360c7728bcadb8d4106ca8b4c98fe4f9 100644 (file)
@@ -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)
index d556afaf49789fad6c07e4587aa73bb984b86a39..469d21f96262582a321898435172a5990bc97b4a 100644 (file)
--- 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)?
 =================================
index 30e6191c6ab8bf323985e1a3ec0c7920226d658d..a0a40e89fc378295f8608afd9344868e1758986b 100644 (file)
@@ -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 */
index 2c033f88f2f5ab3831004fdcbe0228846b8761ea..f95240592e4fbd11fba4613782321ea52f882d73 100644 (file)
@@ -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)
 {