Search code examples
pythonpython-3.xfunctools

functools.reduce in Python not working as expected


I would like to sum across keys of dictionaries nested within a list using the functools.reduce function

I can accomplish this WITHOUT the functools.reduce function with the following simple program:

dict1 = {'a': '1', 'b': '2'}
dict2 = {'a': '5', 'b': '0'}
dict3 = {'a': '7', 'b': '3'}

data_list = [dict1, dict2, dict3]

total_a = 0
total_b = 0
for record in data_list:
    total_a += eval(record['a'])
    total_b += eval(record['b'])

print(total_a)
print(total_b)

As I said however, I would like to produce the same results using the functools.reduce method instead.

Here is my attempt at using functools.reduce with a lambda expression:

from functools import reduce

dict1 = {'a': '1', 'b': '2'}
dict2 = {'a': '5', 'b': '0'}
dict3 = {'a': '7', 'b': '3'}

data_list = [dict1, dict2, dict3]

total_a = reduce(lambda x, y: int(x['a']) + int(y['a']),data_list)
total_b = reduce(lambda x, y: int(x['b']) + int(y['b']),data_list )

print(total_a)
print(total_b)

Unfortunately, I get the following error and do not know why:

TypeError: 'int' object is not subscriptable

Does someone know why I am getting this error?


Solution

  • TypeError: 'int' object is not subscriptable
    

    Does someone know why I am getting this error?

    First, let's reduce (pun intended) the sample to a minimum:

    >>> from functools import reduce
    >>> data = [{"a": 1}, {"a": 2}, {"a": 3}]
    >>> reduce(lambda x, y: x["a"] + y["a"], data)   
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 1, in <lambda>
    TypeError: 'int' object is not subscriptable
    

    Same error. But observe this:

    >>> reduce(lambda x, y: x["a"] + y["a"], data[:2])   
    3
    

    That's working. So what's going on? For simplification, let's assign the lambda expression to a variable:

    f = lambda x, y: x['a'] + y['a']
    

    Reduce combines the input like this:

    # Example: reduce(lambda x, y: x["a"] + y["a"], data[:2])   
    >>> f(data[0], data[1])
    
    # which evaluates in steps like this:
    
    >>> data[0]["a"] + data[1]["a"]
    >>> 1 + 2
    >>> 3
    

    But what's happening when reducing the full list? This evaluates to

    # Example: reduce(lambda x, y: x["a"] + y["a"], data)   
    >>> f(f(data[0], data[1]), data[2])
    
    # which evaluates in steps like this:
    
    >>> f(data[0]["a"] + data[1]["a"], data[2])
    >>> f(1 + 2, data[2])
    >>> f(3, data[2])
    >>> 3["a"] + data[2]["a"]
    

    So this errors out because it tries to access item "a" from the integer 3.

    Basically: The output of the function passed to reduce must be acceptable as its first parameter. In your example, the lambda expects a dictionary as its first parameter and returns an integer.