Skip to content

Commit 9718b59

Browse files
authored
bpo-34659: Adds initial kwarg to itertools.accumulate() (GH-9345)
1 parent c87d9f4 commit 9718b59

5 files changed

Lines changed: 65 additions & 19 deletions

File tree

Doc/library/itertools.rst

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,29 +86,38 @@ The following module functions all construct and return iterators. Some provide
8686
streams of infinite length, so they should only be accessed by functions or
8787
loops that truncate the stream.
8888

89-
.. function:: accumulate(iterable[, func])
89+
.. function:: accumulate(iterable[, func, *, initial=None])
9090

9191
Make an iterator that returns accumulated sums, or accumulated
9292
results of other binary functions (specified via the optional
93-
*func* argument). If *func* is supplied, it should be a function
93+
*func* argument).
94+
95+
If *func* is supplied, it should be a function
9496
of two arguments. Elements of the input *iterable* may be any type
9597
that can be accepted as arguments to *func*. (For example, with
9698
the default operation of addition, elements may be any addable
9799
type including :class:`~decimal.Decimal` or
98-
:class:`~fractions.Fraction`.) If the input iterable is empty, the
99-
output iterable will also be empty.
100+
:class:`~fractions.Fraction`.)
101+
102+
Usually, the number of elements output matches the input iterable.
103+
However, if the keyword argument *initial* is provided, the
104+
accumulation leads off with the *initial* value so that the output
105+
has one more element than the input iterable.
100106

101107
Roughly equivalent to::
102108

103-
def accumulate(iterable, func=operator.add):
109+
def accumulate(iterable, func=operator.add, *, initial=None):
104110
'Return running totals'
105111
# accumulate([1,2,3,4,5]) --> 1 3 6 10 15
112+
# accumulate([1,2,3,4,5], initial=100) --> 100 101 103 106 110 115
106113
# accumulate([1,2,3,4,5], operator.mul) --> 1 2 6 24 120
107114
it = iter(iterable)
108-
try:
109-
total = next(it)
110-
except StopIteration:
111-
return
115+
total = initial
116+
if initial is None:
117+
try:
118+
total = next(it)
119+
except StopIteration:
120+
return
112121
yield total
113122
for element in it:
114123
total = func(total, element)
@@ -152,6 +161,9 @@ loops that truncate the stream.
152161
.. versionchanged:: 3.3
153162
Added the optional *func* parameter.
154163

164+
.. versionchanged:: 3.8
165+
Added the optional *initial* parameter.
166+
155167
.. function:: chain(*iterables)
156168

157169
Make an iterator that returns elements from the first iterable until it is

Lib/test/test_itertools.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,12 @@ def test_accumulate(self):
147147
list(accumulate(s, chr)) # unary-operation
148148
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
149149
self.pickletest(proto, accumulate(range(10))) # test pickling
150+
self.pickletest(proto, accumulate(range(10), initial=7))
151+
self.assertEqual(list(accumulate([10, 5, 1], initial=None)), [10, 15, 16])
152+
self.assertEqual(list(accumulate([10, 5, 1], initial=100)), [100, 110, 115, 116])
153+
self.assertEqual(list(accumulate([], initial=100)), [100])
154+
with self.assertRaises(TypeError):
155+
list(accumulate([10, 20], 100))
150156

151157
def test_chain(self):
152158

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add an optional *initial* argument to itertools.accumulate().

Modules/clinic/itertoolsmodule.c.h

Lines changed: 8 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/itertoolsmodule.c

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3475,6 +3475,7 @@ typedef struct {
34753475
PyObject *total;
34763476
PyObject *it;
34773477
PyObject *binop;
3478+
PyObject *initial;
34783479
} accumulateobject;
34793480

34803481
static PyTypeObject accumulate_type;
@@ -3484,18 +3485,19 @@ static PyTypeObject accumulate_type;
34843485
itertools.accumulate.__new__
34853486
iterable: object
34863487
func as binop: object = None
3488+
*
3489+
initial: object = None
34873490
Return series of accumulated sums (or other binary function results).
34883491
[clinic start generated code]*/
34893492

34903493
static PyObject *
34913494
itertools_accumulate_impl(PyTypeObject *type, PyObject *iterable,
3492-
PyObject *binop)
3493-
/*[clinic end generated code: output=514d0fb30ba14d55 input=6d9d16aaa1d3cbfc]*/
3495+
PyObject *binop, PyObject *initial)
3496+
/*[clinic end generated code: output=66da2650627128f8 input=c4ce20ac59bf7ffd]*/
34943497
{
34953498
PyObject *it;
34963499
accumulateobject *lz;
34973500

3498-
34993501
/* Get iterator. */
35003502
it = PyObject_GetIter(iterable);
35013503
if (it == NULL)
@@ -3514,6 +3516,8 @@ itertools_accumulate_impl(PyTypeObject *type, PyObject *iterable,
35143516
}
35153517
lz->total = NULL;
35163518
lz->it = it;
3519+
Py_XINCREF(initial);
3520+
lz->initial = initial;
35173521
return (PyObject *)lz;
35183522
}
35193523

@@ -3524,6 +3528,7 @@ accumulate_dealloc(accumulateobject *lz)
35243528
Py_XDECREF(lz->binop);
35253529
Py_XDECREF(lz->total);
35263530
Py_XDECREF(lz->it);
3531+
Py_XDECREF(lz->initial);
35273532
Py_TYPE(lz)->tp_free(lz);
35283533
}
35293534

@@ -3533,6 +3538,7 @@ accumulate_traverse(accumulateobject *lz, visitproc visit, void *arg)
35333538
Py_VISIT(lz->binop);
35343539
Py_VISIT(lz->it);
35353540
Py_VISIT(lz->total);
3541+
Py_VISIT(lz->initial);
35363542
return 0;
35373543
}
35383544

@@ -3541,6 +3547,13 @@ accumulate_next(accumulateobject *lz)
35413547
{
35423548
PyObject *val, *newtotal;
35433549

3550+
if (lz->initial != Py_None) {
3551+
lz->total = lz->initial;
3552+
Py_INCREF(Py_None);
3553+
lz->initial = Py_None;
3554+
Py_INCREF(lz->total);
3555+
return lz->total;
3556+
}
35443557
val = (*Py_TYPE(lz->it)->tp_iternext)(lz->it);
35453558
if (val == NULL)
35463559
return NULL;
@@ -3567,6 +3580,19 @@ accumulate_next(accumulateobject *lz)
35673580
static PyObject *
35683581
accumulate_reduce(accumulateobject *lz, PyObject *Py_UNUSED(ignored))
35693582
{
3583+
if (lz->initial != Py_None) {
3584+
PyObject *it;
3585+
3586+
assert(lz->total == NULL);
3587+
if (PyType_Ready(&chain_type) < 0)
3588+
return NULL;
3589+
it = PyObject_CallFunction((PyObject *)&chain_type, "(O)O",
3590+
lz->initial, lz->it);
3591+
if (it == NULL)
3592+
return NULL;
3593+
return Py_BuildValue("O(NO)O", Py_TYPE(lz),
3594+
it, lz->binop?lz->binop:Py_None, Py_None);
3595+
}
35703596
if (lz->total == Py_None) {
35713597
PyObject *it;
35723598

0 commit comments

Comments
 (0)