Search code examples
pythonpython-3.xpython-itertoolsaccumulate

Accumulate does not work with sum, but does work with equivalent lambda function. Why?


I was playing around with the accumulate function, and I thought that accumulate(<int[]>,sum) would yield a cumulative sum. However, running the following code results in an error.

from itertools import accumulate
print([*accumulate([1,2,3],sum)])

Specifically, I get TypeError: 'int' object is not iterable. On the other hand, running the same code with a lambda function that does the exact same thing leads to the expected result.

from itertools import accumulate
print([*accumulate([1,2,3],lambda *args:sum(args))])
# [1, 3, 6]

When I run this code using a named, custom function which does the same thing, I get yet another bizarre result.

from itertools import accumulate    
def my_sum(*args): return sum(args)
print([*accumulate([1,2,3]),my_sum])
#[1, 3, 6, <function my_sum at 0x7fd57139caf0>]

It's not clear what is leading to the difference in behavior. sum,my_sum, and the anonymous function are are of the type "function", so the type alone isn't determining things. I also did the following to see if I could get any other lead; the only difference I noticed is that sum is a built in function.

print(lambda *args:sum(args),my_sum,sum,sep='\n')
# <function <lambda> at 0x7fd57139cb80>
# <function my_sum at 0x7fd57139cc10>
# <built-in function sum>

So what's going on here?


Solution

  • From the docs: for itertools.accumulate(iterable[, func, *, initial=None])

    If func is supplied, it should be a function of two arguments. Elements of the input iterable may be any type that can be accepted as arguments to func. (For example, with the default operation of addition, elements may be any addable type including Decimal or Fraction.)

    sum() does accept two arguments, but the first argument must be an iterable, and the second is the start value. Docs

    Let's see what accumulate() passes to its func argument by printing the args in my_sum()

    def my_sum(*args):
        print(args)
        return sum(args)
    
    accumulate([1, 2, 3], my_sum)
    # (1, 2)
    # (3, 3)
    

    So accumulate() passes the last accumulated value and the next number to func. Since the first argument to sum() must be iterable (which an int is not), you get that error.

    Your lambda is not equivalent to sum(): sum() takes one iterable and returns the sum of its elements. Your lambda takes any number of arguments and returns the sum of those arguments. To test this, see what you get when you do sum([1, 2, 3]), and my_sum([1, 2, 3]).

    In your final example you have a typo. You didn't pass my_sum to accumulate(). You created a list containing the result of accumulate([1, 2, 3]), and then the function my_sum. Fix it to print([*accumulate([1,2,3], my_sum)]) and you get the same output as the lambda case.

    Note that providing no func behaves as if func=operator.add and will give you a cumulative sum.

    >>> accumulate([1, 2, 3])
    [1, 3, 6]