[Python-ideas] Start argument for itertools.accumulate() [Was: Proposal: A Reduce-Map Comprehension and a "last" builtin]

Tim Peters tim.peters at gmail.com
Sat Apr 7 00:06:30 EDT 2018


[Raymond Hettinger <raymond.hettinger at gmail.com>]
> ...
> Q. How readable is the proposed code?
> A. Look at the following code and ask yourself what it does:
>
>         accumulate(range(4, 6), operator.mul, start=6)
>
>    Now test your understanding:
>
>         How many values are emitted?

3

>         What is the first value emitted?

6

>         Are the two sixes related?

No.

>         What is this code trying to accomplish?

It's quite obviously trying to bias the reader against the proposal by
presenting a senseless example ;-)  Assuming there's any real reason
to write that code at all, a better question is whether it's more
comprehensible than

accumulate(itertools.chain([6], range(4, 6)), operator.mul)


> ...
> Q. What would this look like in real code?
> A. We have almost no real-world examples, but here is one from a StackExchange post:
>
>         def wsieve():       # wheel-sieve, by Will Ness.    ideone.com/mqO25A->0hIE89
> ...

By sheer coincidence, I happened to write another yesterday.  This is
from a program looking for the smallest integers that yield new
records for Collatz sequence lengths.  The details don't matter,
except that - like Will Ness's wheel sieve code - it needs to generate
an unbounded increasing sequence of integers with a periodic, but
complicated, sequence of deltas, starting at a more-or-less arbitrary
point.

    def buildtab(SHIFT, LIMIT):
        ...
        # Candidates are of the form i*LIMIT + j, for i >= 1 and j in
        # goodix.  However, a new record can't be set for a number of
        # the form 3k+2:  that's two steps after 2k+1, so the smaller
        # 2k+1 has a glide 2 longer.  We want to arrange to never try
        # numbers of the form 3k+2 to begin with.
        base = 0
        ix2 = []
        for i in range(3):
            base += LIMIT
            for ix in goodix:
                num = base + ix
                if num % 3 != 2:
                    ix2.append(num)
        ix2.append(ix2[0] + 3 * LIMIT)
        assert len(ix2) == 2 * len(goodix) + 1
        del goodix
        deltas = tuple(ix2[i] - ix2[i-1] for i in range(1, len(ix2)))
        return tuple(result), ix2[0], deltas

A note on "complicated":  the tuple of deltas here can contain
millions of integers, and that's the smallest length at which it
becomes periodic.

Later:

    def coll(SHIFT=24):
        ...
        from itertools import accumulate, chain, cycle
        ...
        LIMIT = 1 << SHIFT
        ...
        abc, first, deltas = buildtab(SHIFT, LIMIT)
        ...
        for num in accumulate(chain([first], cycle(deltas))):
            assert num % 3 != 2

As in Will's code, it would be more readable as:

        for num in accumulate(cycle(deltas), start=first):

That says what it does pretty clearly, whereas deducing the behavior
from "OK, it's chaining together a singleton list and a cycle, because
...?" is a bit of a head scratcher at first.

That said, if the need came up often, as you noted it's dead easy to
write a helper function to encapsulate the "head scratcher" part, and
with no significant loss of efficiency.

So I'd be -0 overall, _except_ that "chain together a singleton list
and a cycle" is so obscure on the face of it than I'm not sure most
programmers who wanted the functionality of `start=` would ever think
of it.  I'm not sure that I would have, except that I studied Ness's
wheel sieve code a long time ago and the idea stuck.  So that makes me
+0.4.


More information about the Python-ideas mailing list