summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Peters <tim.peters@gmail.com>2001-05-01 20:45:31 (GMT)
committerTim Peters <tim.peters@gmail.com>2001-05-01 20:45:31 (GMT)
commitf553f89d45f049e32f5eadc0c78a1e2440745d7a (patch)
tree085e1bcc3a073a4faac1e6cf32e88354142b2994
parent47668928e6f02713a1fbd27b434b08b08ca75d1b (diff)
downloadcpython-f553f89d45f049e32f5eadc0c78a1e2440745d7a.zip
cpython-f553f89d45f049e32f5eadc0c78a1e2440745d7a.tar.gz
cpython-f553f89d45f049e32f5eadc0c78a1e2440745d7a.tar.bz2
Generalize list(seq) to work with iterators. This also generalizes list()
to no longer insist that len(seq) be defined. NEEDS DOC CHANGES. This is meant to be a model for how other functions of this ilk (max, filter, etc) can be generalized similarly. Feel encouraged to grab your favorite and convert it! Note some cute consequences: list(file) == file.readlines() == list(file.xreadlines()) list(dict) == dict.keys() list(dict.iteritems()) = dict.items() list(xrange(i, j, k)) == range(i, j, k)
-rw-r--r--Lib/test/test_iter.py32
-rw-r--r--Misc/NEWS10
-rw-r--r--Objects/abstract.c88
3 files changed, 99 insertions, 31 deletions
diff --git a/Lib/test/test_iter.py b/Lib/test/test_iter.py
index d4b8736..5b9bf65 100644
--- a/Lib/test/test_iter.py
+++ b/Lib/test/test_iter.py
@@ -243,4 +243,36 @@ class TestCase(unittest.TestCase):
except OSError:
pass
+ # Test list()'s use of iterators.
+ def test_builtin_list(self):
+ self.assertEqual(list(SequenceClass(5)), range(5))
+ self.assertEqual(list(SequenceClass(0)), [])
+ self.assertEqual(list(()), [])
+ self.assertEqual(list(range(10, -1, -1)), range(10, -1, -1))
+
+ d = {"one": 1, "two": 2, "three": 3}
+ self.assertEqual(list(d), d.keys())
+
+ self.assertRaises(TypeError, list, list)
+ self.assertRaises(TypeError, list, 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(list(f), ["0\n", "1\n", "2\n", "3\n", "4\n"])
+ f.seek(0, 0)
+ self.assertEqual(list(f.xreadlines()),
+ ["0\n", "1\n", "2\n", "3\n", "4\n"])
+ finally:
+ f.close()
+ try:
+ unlink(TESTFN)
+ except OSError:
+ pass
+
run_unittest(TestCase)
diff --git a/Misc/NEWS b/Misc/NEWS
index 92882d2..bce3889 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -1,3 +1,13 @@
+What's New in Python 2.2a0?
+===========================
+
+Core
+
+- The following functions were generalized to work nicely with iterator
+ arguments:
+ list()
+
+
What's New in Python 2.1 (final)?
=================================
diff --git a/Objects/abstract.c b/Objects/abstract.c
index f656747..a5f97a1 100644
--- a/Objects/abstract.c
+++ b/Objects/abstract.c
@@ -1236,52 +1236,78 @@ PySequence_Tuple(PyObject *v)
PyObject *
PySequence_List(PyObject *v)
{
- PySequenceMethods *m;
+ PyObject *it; /* iter(v) */
+ PyObject *result; /* result list */
+ int n; /* guess for result list size */
+ int i;
if (v == NULL)
return null_error();
+ /* Special-case list(a_list), for speed. */
if (PyList_Check(v))
return PyList_GetSlice(v, 0, PyList_GET_SIZE(v));
- m = v->ob_type->tp_as_sequence;
- if (m && m->sq_item) {
- int i;
- PyObject *l;
- int n = PySequence_Size(v);
+ /* Get iterator. There may be some low-level efficiency to be gained
+ * by caching the tp_iternext slot instead of using PyIter_Next()
+ * later, but premature optimization is the root etc.
+ */
+ it = PyObject_GetIter(v);
+ if (it == NULL)
+ return NULL;
+
+ /* Guess a result list size. */
+ n = -1; /* unknown */
+ if (PySequence_Check(v) &&
+ v->ob_type->tp_as_sequence->sq_length) {
+ n = PySequence_Size(v);
if (n < 0)
- return NULL;
- l = PyList_New(n);
- if (l == NULL)
- return NULL;
- for (i = 0; ; i++) {
- PyObject *item = (*m->sq_item)(v, i);
- if (item == NULL) {
- if (PyErr_ExceptionMatches(PyExc_IndexError))
+ PyErr_Clear();
+ }
+ if (n < 0)
+ n = 8; /* arbitrary */
+ result = PyList_New(n);
+ if (result == NULL) {
+ Py_DECREF(it);
+ return NULL;
+ }
+
+ /* Run iterator to exhaustion. */
+ for (i = 0; ; i++) {
+ PyObject *item = PyIter_Next(it);
+ if (item == NULL) {
+ /* We're out of here in any case, but if this is a
+ * StopIteration exception it's expected, but if
+ * any other kind of exception it's an error.
+ */
+ if (PyErr_Occurred()) {
+ if (PyErr_ExceptionMatches(PyExc_StopIteration))
PyErr_Clear();
else {
- Py_DECREF(l);
- l = NULL;
+ Py_DECREF(result);
+ result = NULL;
}
- break;
- }
- if (i < n)
- PyList_SET_ITEM(l, i, item);
- else if (PyList_Append(l, item) < 0) {
- Py_DECREF(l);
- l = NULL;
- break;
}
+ break;
}
- if (i < n && l != NULL) {
- if (PyList_SetSlice(l, i, n, (PyObject *)NULL) != 0) {
- Py_DECREF(l);
- l = NULL;
- }
+ if (i < n)
+ PyList_SET_ITEM(result, i, item);
+ else if (PyList_Append(result, item) < 0) {
+ Py_DECREF(result);
+ result = NULL;
+ break;
}
- return l;
}
- return type_error("list() argument must be a sequence");
+
+ /* Cut back result list if initial guess was too large. */
+ if (i < n && result != NULL) {
+ if (PyList_SetSlice(result, i, n, (PyObject *)NULL) != 0) {
+ Py_DECREF(result);
+ result = NULL;
+ }
+ }
+ Py_DECREF(it);
+ return result;
}
PyObject *