From de9725f1352856b7d6de1bb29383a7be5f181740 Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Sat, 5 May 2001 10:06:17 +0000 Subject: 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__. --- Lib/test/test_contains.py | 4 +-- Lib/test/test_iter.py | 55 +++++++++++++++++++++++++++++++++++++++++ Misc/NEWS | 4 +-- Objects/abstract.c | 63 +++++++++++++++++++++++++---------------------- Objects/object.c | 1 + 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) { -- cgit v0.12