diff options
author | Tim Peters <tim.peters@gmail.com> | 2001-05-05 03:56:37 (GMT) |
---|---|---|
committer | Tim Peters <tim.peters@gmail.com> | 2001-05-05 03:56:37 (GMT) |
commit | 6912d4ddf0504a3d5611ddd12cbde3354bd48279 (patch) | |
tree | af9162f00d5169fc8e9c13c0046d61051c2463ed | |
parent | f4848dac41689d1f2f8bd224bd935beae9b8df86 (diff) | |
download | cpython-6912d4ddf0504a3d5611ddd12cbde3354bd48279.zip cpython-6912d4ddf0504a3d5611ddd12cbde3354bd48279.tar.gz cpython-6912d4ddf0504a3d5611ddd12cbde3354bd48279.tar.bz2 |
Generalize tuple() to work nicely with iterators.
NEEDS DOC CHANGES.
This one surprised me! While I expected tuple() to be a no-brainer, turns
out it's actually dripping with consequences:
1. It will *allow* the popular PySequence_Fast() to work with any iterable
object (code for that not yet checked in, but should be trivial).
2. It caused two std tests to fail. This because some places used
PyTuple_Sequence() (the C spelling of tuple()) as an indirect way to test
whether something *is* a sequence. But tuple() code only looked for the
existence of sq->item to determine that, and e.g. an instance passed
that test whether or not it supported the other operations tuple()
needed (e.g., __len__). So some things the tests *expected* to fail
with an AttributeError now fail with a TypeError instead. This looks
like an improvement to me; e.g., test_coercion used to produce 559
TypeErrors and 2 AttributeErrors, and now they're all TypeErrors. The
error details are more informative too, because the places calling this
were *looking* for TypeErrors in order to replace the generic tuple()
"not a sequence" msg with their own more specific text, and
AttributeErrors snuck by that.
-rw-r--r-- | Include/abstract.h | 2 | ||||
-rw-r--r-- | Lib/test/output/test_coercion | 4 | ||||
-rw-r--r-- | Lib/test/test_extcall.py | 8 | ||||
-rw-r--r-- | Lib/test/test_iter.py | 33 | ||||
-rw-r--r-- | Misc/NEWS | 4 | ||||
-rw-r--r-- | Objects/abstract.c | 87 |
6 files changed, 89 insertions, 49 deletions
diff --git a/Include/abstract.h b/Include/abstract.h index ac9e568..d5f4a99 100644 --- a/Include/abstract.h +++ b/Include/abstract.h @@ -911,7 +911,7 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/ tuple or list. Use PySequence_Fast_GET_ITEM to access the members of this list. - Returns NULL on failure. If the object is not a sequence, + Returns NULL on failure. If the object does not support iteration, raises a TypeError exception with m as the message text. */ diff --git a/Lib/test/output/test_coercion b/Lib/test/output/test_coercion index 8c5f6e0..4209750 100644 --- a/Lib/test/output/test_coercion +++ b/Lib/test/output/test_coercion @@ -516,7 +516,7 @@ test_coercion [1] % None ... exceptions.TypeError [1] %= None ... exceptions.TypeError [1] + <MethodNumber 1> ... exceptions.TypeError -[1] += <MethodNumber 1> ... exceptions.AttributeError +[1] += <MethodNumber 1> ... exceptions.TypeError [1] - <MethodNumber 1> ... exceptions.TypeError [1] -= <MethodNumber 1> ... exceptions.TypeError [1] * <MethodNumber 1> = [1] @@ -528,7 +528,7 @@ test_coercion [1] % <MethodNumber 1> ... exceptions.TypeError [1] %= <MethodNumber 1> ... exceptions.TypeError [1] + <CoerceNumber 2> ... exceptions.TypeError -[1] += <CoerceNumber 2> ... exceptions.AttributeError +[1] += <CoerceNumber 2> ... exceptions.TypeError [1] - <CoerceNumber 2> ... exceptions.TypeError [1] -= <CoerceNumber 2> ... exceptions.TypeError [1] * <CoerceNumber 2> = [1, 1] diff --git a/Lib/test/test_extcall.py b/Lib/test/test_extcall.py index 4720901..274e943 100644 --- a/Lib/test/test_extcall.py +++ b/Lib/test/test_extcall.py @@ -58,20 +58,20 @@ g(1, 2, 3, *(4, 5)) class Nothing: pass try: g(*Nothing()) -except AttributeError, attr: +except TypeError, attr: pass else: - print "should raise AttributeError: __len__" + print "should raise TypeError" class Nothing: def __len__(self): return 5 try: g(*Nothing()) -except AttributeError, attr: +except TypeError, attr: pass else: - print "should raise AttributeError: __getitem__" + print "should raise TypeError" class Nothing: def __len__(self): diff --git a/Lib/test/test_iter.py b/Lib/test/test_iter.py index 5584587..bfe032f 100644 --- a/Lib/test/test_iter.py +++ b/Lib/test/test_iter.py @@ -275,6 +275,39 @@ class TestCase(unittest.TestCase): except OSError: pass + # Test tuples()'s use of iterators. + def test_builtin_tuple(self): + self.assertEqual(tuple(SequenceClass(5)), (0, 1, 2, 3, 4)) + self.assertEqual(tuple(SequenceClass(0)), ()) + self.assertEqual(tuple([]), ()) + self.assertEqual(tuple(()), ()) + self.assertEqual(tuple("abc"), ("a", "b", "c")) + + d = {"one": 1, "two": 2, "three": 3} + self.assertEqual(tuple(d), tuple(d.keys())) + + self.assertRaises(TypeError, tuple, list) + self.assertRaises(TypeError, tuple, 42) + + f = open(TESTFN, "w") + try: + for i in range(5): + f.write("%d\n" % i) + finally: + f.close() + f = open(TESTFN, "r") + try: + self.assertEqual(tuple(f), ("0\n", "1\n", "2\n", "3\n", "4\n")) + f.seek(0, 0) + self.assertEqual(tuple(f.xreadlines()), + ("0\n", "1\n", "2\n", "3\n", "4\n")) + finally: + f.close() + try: + unlink(TESTFN) + except OSError: + pass + # Test filter()'s use of iterators. def test_builtin_filter(self): self.assertEqual(filter(None, SequenceClass(5)), range(1, 5)) @@ -24,9 +24,9 @@ Core min() reduce() XXX TODO string.join(), unicode.join() - XXX TODO tuple() + tuple() XXX TODO zip() - XXX TODO 'x in y' (!) (?) + XXX TODO 'x in y' What's New in Python 2.1 (final)? ================================= diff --git a/Objects/abstract.c b/Objects/abstract.c index 7133867..2fe6b1f 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1176,61 +1176,68 @@ PySequence_DelSlice(PyObject *s, int i1, int i2) PyObject * PySequence_Tuple(PyObject *v) { - PySequenceMethods *m; + PyObject *it; /* iter(v) */ + int n; /* guess for result tuple size */ + PyObject *result; + int j; if (v == NULL) return null_error(); + /* Special-case the common tuple and list cases, for efficiency. */ if (PyTuple_Check(v)) { Py_INCREF(v); return v; } - if (PyList_Check(v)) return PyList_AsTuple(v); - /* There used to be code for strings here, but tuplifying strings is - not a common activity, so I nuked it. Down with code bloat! */ + /* Get iterator. */ + it = PyObject_GetIter(v); + if (it == NULL) + return type_error("tuple() argument must support iteration"); - /* Generic sequence object */ - m = v->ob_type->tp_as_sequence; - if (m && m->sq_item) { - int i; - PyObject *t; - int n = PySequence_Size(v); - if (n < 0) - return NULL; - t = PyTuple_New(n); - if (t == NULL) - return NULL; - for (i = 0; ; i++) { - PyObject *item = (*m->sq_item)(v, i); - if (item == NULL) { - if (PyErr_ExceptionMatches(PyExc_IndexError)) - PyErr_Clear(); - else { - Py_DECREF(t); - t = NULL; - } - break; - } - if (i >= n) { - if (n < 500) - n += 10; - else - n += 100; - if (_PyTuple_Resize(&t, n, 0) != 0) - break; - } - PyTuple_SET_ITEM(t, i, item); + /* Guess result size and allocate space. */ + n = PySequence_Size(v); + if (n < 0) { + PyErr_Clear(); + n = 10; /* arbitrary */ + } + result = PyTuple_New(n); + if (result == NULL) + goto Fail; + + /* Fill the tuple. */ + for (j = 0; ; ++j) { + PyObject *item = PyIter_Next(it); + if (item == NULL) { + if (PyErr_Occurred()) + goto Fail; + break; + } + if (j >= n) { + if (n < 500) + n += 10; + else + n += 100; + if (_PyTuple_Resize(&result, n, 0) != 0) + goto Fail; } - if (i < n && t != NULL) - _PyTuple_Resize(&t, i, 0); - return t; + PyTuple_SET_ITEM(result, j, item); } - /* None of the above */ - return type_error("tuple() argument must be a sequence"); + /* Cut tuple back if guess was too large. */ + if (j < n && + _PyTuple_Resize(&result, j, 0) != 0) + goto Fail; + + Py_DECREF(it); + return result; + +Fail: + Py_XDECREF(result); + Py_DECREF(it); + return NULL; } PyObject * |