Given that two respected members of the community so strongly disagree whether accumulate([], start=0) should behave like accumulate([]) or like accumulate([0]), maybe in the end it's better not to add a start argument. (The disagreement suggests that we can't trust users' intuition here.)

On Sat, Apr 7, 2018 at 9:14 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 8 April 2018 at 13:17, Tim Peters <tim.peters@gmail.com> wrote:
> [Nick Coghlan <ncoghlan@gmail.com>]
>> So I now think that having "start" as a parameter to one but not the
>> other, counts as a genuine API discrepancy.
>
> Genuine but minor ;-)

Agreed :)

>> Providing start to accumulate would then mean the same thing as
>> providing it to sum(): it would change the basis point for the first
>> addition operation, but it wouldn't change the *number* of cumulative
>> sums produced.
>
> That makes no sense to me.  `sum()` with a `start` argument always
> returns a single result, even if the iterable is empty.
>
>>>> sum([], 42)
> 42

Right, but if itertools.accumulate() had the semantics of starting
with a sum() over an empty iterable, then it would always start with
an initial zero.

It doesn't - it starts with "0+first_item", so the length of the
output iterator matches the number of items in the input iterable:

    >>> list(accumulate([]))
    []
    >>> list(accumulate([1, 2, 3, 4]))
    [1, 3, 6, 10]

That matches the output you'd get from a naive O(n^2) implementation
of cumulative sums:

    data = list(iterable)
    for stop in range(1, len(iterable)):
        yield sum(data[:stop])

So if the new parameter were to be called start, then I'd expect the
semantics to be equivalent to:

    data = list(iterable)
    for stop in range(1, len(iterable)):
        yield sum(data[:stop], start=start)

rather than the version Raymond posted at the top of the thread (where
setting start explicitly also implicitly increases the number of items
produced).

That concern mostly goes away if the new parameter is deliberately
called something *other than* "start" (e.g. "prepend=value", or
"first=value"), but it could also be addressed by offering a dedicated
"yield_start" toggle, such that the revised semantics were:

        def accumulate(iterable, func=operator.add, start=0, yield_start=False):
            it = iter(iterable)
            total = start
            if yield_start:
                yield total
            for element in it:
                total = func(total, element)
                yield total

That approach would have the advantage of making the default value of
"start" much easier to document (since it would just be zero, the same
as it is for sum()), and only the length of the input iterable and
"yield_start" would affect how many partial sums were produced.

Cheers,
Nick.

--
Nick Coghlan   |   ncoghlan@gmail.com   |   Brisbane, Australia
_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/



--
--Guido van Rossum (python.org/~guido)