Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4e9afdc

Browse files
committed
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.
1 parent 6aebded commit 4e9afdc

3 files changed

Lines changed: 104 additions & 66 deletions

File tree

Lib/test/test_iter.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,4 +351,39 @@ def test_builtin_max_min(self):
351351
except OSError:
352352
pass
353353

354+
# Test map()'s use of iterators.
355+
def test_builtin_map(self):
356+
self.assertEqual(map(None, SequenceClass(5)), range(5))
357+
self.assertEqual(map(lambda x: x+1, SequenceClass(5)), range(1, 6))
358+
359+
d = {"one": 1, "two": 2, "three": 3}
360+
self.assertEqual(map(None, d), d.keys())
361+
self.assertEqual(map(lambda k, d=d: (k, d[k]), d), d.items())
362+
dkeys = d.keys()
363+
expected = [(i < len(d) and dkeys[i] or None,
364+
i,
365+
i < len(d) and dkeys[i] or None)
366+
for i in range(5)]
367+
self.assertEqual(map(None, d,
368+
SequenceClass(5),
369+
iter(d.iterkeys())),
370+
expected)
371+
372+
f = open(TESTFN, "w")
373+
try:
374+
for i in range(10):
375+
f.write("xy" * i + "\n") # line i has len 2*i+1
376+
finally:
377+
f.close()
378+
f = open(TESTFN, "r")
379+
try:
380+
self.assertEqual(map(len, f), range(1, 21, 2))
381+
f.seek(0, 0)
382+
finally:
383+
f.close()
384+
try:
385+
unlink(TESTFN)
386+
except OSError:
387+
pass
388+
354389
run_unittest(TestCase)

Misc/NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Core
1919
arguments:
2020
filter()
2121
list()
22+
map()
2223
max()
2324
min()
2425

Python/bltinmodule.c

Lines changed: 68 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -936,9 +936,8 @@ static PyObject *
936936
builtin_map(PyObject *self, PyObject *args)
937937
{
938938
typedef struct {
939-
PyObject *seq;
940-
PySequenceMethods *sqf;
941-
int saw_IndexError;
939+
PyObject *it; /* the iterator object */
940+
int saw_StopIteration; /* bool: did the iterator end? */
942941
} sequence;
943942

944943
PyObject *func, *result;
@@ -961,104 +960,105 @@ builtin_map(PyObject *self, PyObject *args)
961960
return PySequence_List(PyTuple_GetItem(args, 1));
962961
}
963962

963+
/* Get space for sequence descriptors. Must NULL out the iterator
964+
* pointers so that jumping to Fail_2 later doesn't see trash.
965+
*/
964966
if ((seqs = PyMem_NEW(sequence, n)) == NULL) {
965967
PyErr_NoMemory();
966-
goto Fail_2;
968+
return NULL;
969+
}
970+
for (i = 0; i < n; ++i) {
971+
seqs[i].it = (PyObject*)NULL;
972+
seqs[i].saw_StopIteration = 0;
967973
}
968974

969-
/* Do a first pass to (a) verify the args are sequences; (b) set
970-
* len to the largest of their lengths; (c) initialize the seqs
971-
* descriptor vector.
975+
/* Do a first pass to obtain iterators for the arguments, and set len
976+
* to the largest of their lengths.
972977
*/
973-
for (len = 0, i = 0, sqp = seqs; i < n; ++i, ++sqp) {
978+
len = 0;
979+
for (i = 0, sqp = seqs; i < n; ++i, ++sqp) {
980+
PyObject *curseq;
974981
int curlen;
975-
PySequenceMethods *sqf;
976982

977-
if ((sqp->seq = PyTuple_GetItem(args, i + 1)) == NULL)
978-
goto Fail_2;
979-
980-
sqp->saw_IndexError = 0;
981-
982-
sqp->sqf = sqf = sqp->seq->ob_type->tp_as_sequence;
983-
if (sqf == NULL ||
984-
sqf->sq_item == NULL)
985-
{
983+
/* Get iterator. */
984+
curseq = PyTuple_GetItem(args, i+1);
985+
sqp->it = PyObject_GetIter(curseq);
986+
if (sqp->it == NULL) {
986987
static char errmsg[] =
987-
"argument %d to map() must be a sequence object";
988+
"argument %d to map() must support iteration";
988989
char errbuf[sizeof(errmsg) + 25];
989-
990990
sprintf(errbuf, errmsg, i+2);
991991
PyErr_SetString(PyExc_TypeError, errbuf);
992992
goto Fail_2;
993993
}
994994

995-
if (sqf->sq_length == NULL)
996-
/* doesn't matter -- make something up */
997-
curlen = 8;
998-
else
999-
curlen = (*sqf->sq_length)(sqp->seq);
995+
/* Update len. */
996+
curlen = -1; /* unknown */
997+
if (PySequence_Check(curseq) &&
998+
curseq->ob_type->tp_as_sequence->sq_length) {
999+
curlen = PySequence_Size(curseq);
1000+
if (curlen < 0)
1001+
PyErr_Clear();
1002+
}
10001003
if (curlen < 0)
1001-
goto Fail_2;
1004+
curlen = 8; /* arbitrary */
10021005
if (curlen > len)
10031006
len = curlen;
10041007
}
10051008

1009+
/* Get space for the result list. */
10061010
if ((result = (PyObject *) PyList_New(len)) == NULL)
10071011
goto Fail_2;
10081012

1009-
/* Iterate over the sequences until all have raised IndexError. */
1013+
/* Iterate over the sequences until all have stopped. */
10101014
for (i = 0; ; ++i) {
10111015
PyObject *alist, *item=NULL, *value;
1012-
int any = 0;
1016+
int numactive = 0;
10131017

10141018
if (func == Py_None && n == 1)
10151019
alist = NULL;
1016-
else {
1017-
if ((alist = PyTuple_New(n)) == NULL)
1018-
goto Fail_1;
1019-
}
1020+
else if ((alist = PyTuple_New(n)) == NULL)
1021+
goto Fail_1;
10201022

10211023
for (j = 0, sqp = seqs; j < n; ++j, ++sqp) {
1022-
if (sqp->saw_IndexError) {
1024+
if (sqp->saw_StopIteration) {
10231025
Py_INCREF(Py_None);
10241026
item = Py_None;
10251027
}
10261028
else {
1027-
item = (*sqp->sqf->sq_item)(sqp->seq, i);
1028-
if (item == NULL) {
1029-
if (PyErr_ExceptionMatches(
1030-
PyExc_IndexError))
1031-
{
1032-
PyErr_Clear();
1033-
Py_INCREF(Py_None);
1034-
item = Py_None;
1035-
sqp->saw_IndexError = 1;
1036-
}
1037-
else {
1038-
goto Fail_0;
1029+
item = PyIter_Next(sqp->it);
1030+
if (item)
1031+
++numactive;
1032+
else {
1033+
/* StopIteration is *implied* by a
1034+
* NULL return from PyIter_Next() if
1035+
* PyErr_Occurred() is false.
1036+
*/
1037+
if (PyErr_Occurred()) {
1038+
if (PyErr_ExceptionMatches(
1039+
PyExc_StopIteration))
1040+
PyErr_Clear();
1041+
else {
1042+
Py_XDECREF(alist);
1043+
goto Fail_1;
1044+
}
10391045
}
1046+
Py_INCREF(Py_None);
1047+
item = Py_None;
1048+
sqp->saw_StopIteration = 1;
10401049
}
1041-
else
1042-
any = 1;
10431050

10441051
}
1045-
if (!alist)
1052+
if (alist)
1053+
PyTuple_SET_ITEM(alist, j, item);
1054+
else
10461055
break;
1047-
if (PyTuple_SetItem(alist, j, item) < 0) {
1048-
Py_DECREF(item);
1049-
goto Fail_0;
1050-
}
1051-
continue;
1052-
1053-
Fail_0:
1054-
Py_XDECREF(alist);
1055-
goto Fail_1;
10561056
}
10571057

10581058
if (!alist)
10591059
alist = item;
10601060

1061-
if (!any) {
1061+
if (numactive == 0) {
10621062
Py_DECREF(alist);
10631063
break;
10641064
}
@@ -1077,23 +1077,25 @@ builtin_map(PyObject *self, PyObject *args)
10771077
if (status < 0)
10781078
goto Fail_1;
10791079
}
1080-
else {
1081-
if (PyList_SetItem(result, i, value) < 0)
1082-
goto Fail_1;
1083-
}
1080+
else if (PyList_SetItem(result, i, value) < 0)
1081+
goto Fail_1;
10841082
}
10851083

10861084
if (i < len && PyList_SetSlice(result, i, len, NULL) < 0)
10871085
goto Fail_1;
10881086

1089-
PyMem_DEL(seqs);
1090-
return result;
1087+
goto Succeed;
10911088

10921089
Fail_1:
10931090
Py_DECREF(result);
10941091
Fail_2:
1095-
if (seqs) PyMem_DEL(seqs);
1096-
return NULL;
1092+
result = NULL;
1093+
Succeed:
1094+
assert(seqs);
1095+
for (i = 0; i < n; ++i)
1096+
Py_XDECREF(seqs[i].it);
1097+
PyMem_DEL(seqs);
1098+
return result;
10971099
}
10981100

10991101
static char map_doc[] =

0 commit comments

Comments
 (0)