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:
parent
2cfe368283
commit
de9725f135
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)?
|
||||||
=================================
|
=================================
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue