Commit 405d4f21 authored by Tim Peters's avatar Tim Peters

Generalize map() to work with iterators.

NEEDS DOC CHANGES.
Possibly contentious:  The first time s.next() yields StopIteration (for
a given map argument s) is the last time map() *tries* s.next().  That
is, if other sequence args are longer, s will never again contribute
anything but None values to the result, even if trying s.next() again
could yield another result.  This is the same behavior map() used to have
wrt IndexError, so it's the only way to be wholly backward-compatible.
I'm not a fan of letting StopIteration mean "try again later" anyway.
parent 06e224bd
...@@ -351,4 +351,39 @@ class TestCase(unittest.TestCase): ...@@ -351,4 +351,39 @@ class TestCase(unittest.TestCase):
except OSError: except OSError:
pass pass
# Test map()'s use of iterators.
def test_builtin_map(self):
self.assertEqual(map(None, SequenceClass(5)), range(5))
self.assertEqual(map(lambda x: x+1, SequenceClass(5)), range(1, 6))
d = {"one": 1, "two": 2, "three": 3}
self.assertEqual(map(None, d), d.keys())
self.assertEqual(map(lambda k, d=d: (k, d[k]), d), d.items())
dkeys = d.keys()
expected = [(i < len(d) and dkeys[i] or None,
i,
i < len(d) and dkeys[i] or None)
for i in range(5)]
self.assertEqual(map(None, d,
SequenceClass(5),
iter(d.iterkeys())),
expected)
f = open(TESTFN, "w")
try:
for i in range(10):
f.write("xy" * i + "\n") # line i has len 2*i+1
finally:
f.close()
f = open(TESTFN, "r")
try:
self.assertEqual(map(len, f), range(1, 21, 2))
f.seek(0, 0)
finally:
f.close()
try:
unlink(TESTFN)
except OSError:
pass
run_unittest(TestCase) run_unittest(TestCase)
...@@ -19,6 +19,7 @@ Core ...@@ -19,6 +19,7 @@ Core
arguments: arguments:
filter() filter()
list() list()
map()
max() max()
min() min()
......
...@@ -936,9 +936,8 @@ static PyObject * ...@@ -936,9 +936,8 @@ static PyObject *
builtin_map(PyObject *self, PyObject *args) builtin_map(PyObject *self, PyObject *args)
{ {
typedef struct { typedef struct {
PyObject *seq; PyObject *it; /* the iterator object */
PySequenceMethods *sqf; int saw_StopIteration; /* bool: did the iterator end? */
int saw_IndexError;
} sequence; } sequence;
PyObject *func, *result; PyObject *func, *result;
...@@ -961,104 +960,105 @@ builtin_map(PyObject *self, PyObject *args) ...@@ -961,104 +960,105 @@ builtin_map(PyObject *self, PyObject *args)
return PySequence_List(PyTuple_GetItem(args, 1)); return PySequence_List(PyTuple_GetItem(args, 1));
} }
/* Get space for sequence descriptors. Must NULL out the iterator
* pointers so that jumping to Fail_2 later doesn't see trash.
*/
if ((seqs = PyMem_NEW(sequence, n)) == NULL) { if ((seqs = PyMem_NEW(sequence, n)) == NULL) {
PyErr_NoMemory(); PyErr_NoMemory();
goto Fail_2; return NULL;
}
for (i = 0; i < n; ++i) {
seqs[i].it = (PyObject*)NULL;
seqs[i].saw_StopIteration = 0;
} }
/* Do a first pass to (a) verify the args are sequences; (b) set /* Do a first pass to obtain iterators for the arguments, and set len
* len to the largest of their lengths; (c) initialize the seqs * to the largest of their lengths.
* descriptor vector.
*/ */
for (len = 0, i = 0, sqp = seqs; i < n; ++i, ++sqp) { len = 0;
for (i = 0, sqp = seqs; i < n; ++i, ++sqp) {
PyObject *curseq;
int curlen; int curlen;
PySequenceMethods *sqf;
if ((sqp->seq = PyTuple_GetItem(args, i + 1)) == NULL) /* Get iterator. */
goto Fail_2; curseq = PyTuple_GetItem(args, i+1);
sqp->it = PyObject_GetIter(curseq);
sqp->saw_IndexError = 0; if (sqp->it == NULL) {
sqp->sqf = sqf = sqp->seq->ob_type->tp_as_sequence;
if (sqf == NULL ||
sqf->sq_item == NULL)
{
static char errmsg[] = static char errmsg[] =
"argument %d to map() must be a sequence object"; "argument %d to map() must support iteration";
char errbuf[sizeof(errmsg) + 25]; char errbuf[sizeof(errmsg) + 25];
sprintf(errbuf, errmsg, i+2); sprintf(errbuf, errmsg, i+2);
PyErr_SetString(PyExc_TypeError, errbuf); PyErr_SetString(PyExc_TypeError, errbuf);
goto Fail_2; goto Fail_2;
} }
if (sqf->sq_length == NULL) /* Update len. */
/* doesn't matter -- make something up */ curlen = -1; /* unknown */
curlen = 8; if (PySequence_Check(curseq) &&
else curseq->ob_type->tp_as_sequence->sq_length) {
curlen = (*sqf->sq_length)(sqp->seq); curlen = PySequence_Size(curseq);
if (curlen < 0)
PyErr_Clear();
}
if (curlen < 0) if (curlen < 0)
goto Fail_2; curlen = 8; /* arbitrary */
if (curlen > len) if (curlen > len)
len = curlen; len = curlen;
} }
/* Get space for the result list. */
if ((result = (PyObject *) PyList_New(len)) == NULL) if ((result = (PyObject *) PyList_New(len)) == NULL)
goto Fail_2; goto Fail_2;
/* Iterate over the sequences until all have raised IndexError. */ /* Iterate over the sequences until all have stopped. */
for (i = 0; ; ++i) { for (i = 0; ; ++i) {
PyObject *alist, *item=NULL, *value; PyObject *alist, *item=NULL, *value;
int any = 0; int numactive = 0;
if (func == Py_None && n == 1) if (func == Py_None && n == 1)
alist = NULL; alist = NULL;
else { else if ((alist = PyTuple_New(n)) == NULL)
if ((alist = PyTuple_New(n)) == NULL) goto Fail_1;
goto Fail_1;
}
for (j = 0, sqp = seqs; j < n; ++j, ++sqp) { for (j = 0, sqp = seqs; j < n; ++j, ++sqp) {
if (sqp->saw_IndexError) { if (sqp->saw_StopIteration) {
Py_INCREF(Py_None); Py_INCREF(Py_None);
item = Py_None; item = Py_None;
} }
else { else {
item = (*sqp->sqf->sq_item)(sqp->seq, i); item = PyIter_Next(sqp->it);
if (item == NULL) { if (item)
if (PyErr_ExceptionMatches( ++numactive;
PyExc_IndexError)) else {
{ /* StopIteration is *implied* by a
PyErr_Clear(); * NULL return from PyIter_Next() if
Py_INCREF(Py_None); * PyErr_Occurred() is false.
item = Py_None; */
sqp->saw_IndexError = 1; if (PyErr_Occurred()) {
} if (PyErr_ExceptionMatches(
else { PyExc_StopIteration))
goto Fail_0; PyErr_Clear();
else {
Py_XDECREF(alist);
goto Fail_1;
}
} }
Py_INCREF(Py_None);
item = Py_None;
sqp->saw_StopIteration = 1;
} }
else
any = 1;
} }
if (!alist) if (alist)
PyTuple_SET_ITEM(alist, j, item);
else
break; break;
if (PyTuple_SetItem(alist, j, item) < 0) {
Py_DECREF(item);
goto Fail_0;
}
continue;
Fail_0:
Py_XDECREF(alist);
goto Fail_1;
} }
if (!alist) if (!alist)
alist = item; alist = item;
if (!any) { if (numactive == 0) {
Py_DECREF(alist); Py_DECREF(alist);
break; break;
} }
...@@ -1077,23 +1077,25 @@ builtin_map(PyObject *self, PyObject *args) ...@@ -1077,23 +1077,25 @@ builtin_map(PyObject *self, PyObject *args)
if (status < 0) if (status < 0)
goto Fail_1; goto Fail_1;
} }
else { else if (PyList_SetItem(result, i, value) < 0)
if (PyList_SetItem(result, i, value) < 0) goto Fail_1;
goto Fail_1;
}
} }
if (i < len && PyList_SetSlice(result, i, len, NULL) < 0) if (i < len && PyList_SetSlice(result, i, len, NULL) < 0)
goto Fail_1; goto Fail_1;
PyMem_DEL(seqs); goto Succeed;
return result;
Fail_1: Fail_1:
Py_DECREF(result); Py_DECREF(result);
Fail_2: Fail_2:
if (seqs) PyMem_DEL(seqs); result = NULL;
return NULL; Succeed:
assert(seqs);
for (i = 0; i < n; ++i)
Py_XDECREF(seqs[i].it);
PyMem_DEL(seqs);
return result;
} }
static char map_doc[] = static char map_doc[] =
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment