python's sum() and non-integer values
Asked Answered
V

7

37

Is there a simple and quick way to use sum() with non-integer values?

So I can use it like this:

class Foo(object):
    def __init__(self,bar)
        self.bar=bar

mylist=[Foo(3),Foo(34),Foo(63),200]
result=sum(mylist) # result should be 300

I tried overriding __add__ and __int__ etc, but I don't have found a solution yet

EDIT:

The solution is to implement:

 def __radd__(self, other):
    return other + self.bar

as Will suggested in his post. But as always, all roads lead to Rome, but I think this is the best solution since I don't need __add__ in my class

Variate answered 2/8, 2009 at 11:27 Comment(0)
B
39

Its a bit tricky - the sum() function takes the start and adds it to the next and so on

You need to implement the __radd__ method:

class T:
    def __init__(self,x):
        self.x = x
    def __radd__(self, other):
        return other + self.x

test = (T(1),T(2),T(3),200)
print sum(test)
Bronez answered 2/8, 2009 at 11:45 Comment(1)
If want to use any other type other than int, you can use sum(test, start=T(0)) otherwise you will get an error TypeError: unsupported operand type(s) for +: 'int' and 'str'Carolynncarolynne
R
28

You may also need to implement the __radd__ function, which represents "reverse add" and is called when the arguments can't be resolved in the "forward" direction. For example, x + y is evaluated as x.__add__(y) if possible, but if that doesn't exist then Python tries y.__radd__(x).

Since the sum() function starts with the integer 0, the first thing it does is try to evaluate:

0 + Foo(3)

which will require that you implement Foo.__radd__.

Reorientation answered 2/8, 2009 at 11:42 Comment(1)
Knowing to account for 0 was extremely helpful for my use case where I'm creating a complex data structure that is combinable with itself via "addition". Thanks so much for the thorough answer!Thunderpeal
P
8

Try:

import operator
result=reduce(operator.add, mylist)

sum() works probably faster, but it is specialized for builtin numbers only. Of course you still have to provide a method to add your Foo() objects. So full example:

class Foo(object):
    def __init__(self, i): self.i = i
    def __add__(self, other):
        if isinstance(other, int):
            return Foo(self.i + other)
        return Foo(self.i + other.i)
    def __radd__(self, other):
        return self.__add__(other)

import operator
mylist = [Foo(42), Foo(36), Foo(12), 177, Foo(11)]
print reduce(operator.add, mylist).i
Pickmeup answered 2/8, 2009 at 11:36 Comment(2)
from functools import reduce # Py3Sedgewick
This is the most generic solution, which works for any type implementing __add__, including those that are not designed to work with sum(...), such as quantities with units in the pint library.Hairsplitting
A
8

Or if you don't want to import anything,

result = reduce((lambda x,y:x+y), mylist)

Another small advantage is that you don't have to necessarily declare an __add__ method as part of your Foo objects, if this happens to be the only circumstance in which you'd want to do addition. (But it probably wouldn't hurt to define __add__ for future flexibility.)

Appease answered 2/8, 2009 at 11:41 Comment(1)
This has a subtle problem: if the list contains only one element, then (lambda x,y: x+y) will never be called and reduce will return that single element. If that single element is an object rather than an integer, the result is an object rather than the sum.Vani
C
6

Try using the __int__ method and then mapping each element in your list to the int function to get the values out:

class Foo(object):
    def __init__(self,bar):
        self.bar = bar
    def __int__(self):
        return self.bar

mylist = [Foo(3),Foo(34),Foo(63),200]
result = sum(map(int,mylist))
print(result)
Circumjacent answered 2/8, 2009 at 11:40 Comment(0)
D
0

sum() (without integer hack) solution seems the cleanest; because sum() starts with the zero, handle this case (from http://www.marinamele.com/2014/04/modifying-add-method-of-python-class.html):

def __radd__(self, other):
    if other == 0:
        return self
    else:
        return other.__add__(self)
Deeannadeeanne answered 1/5, 2020 at 13:20 Comment(0)
U
0

For my particular problem, I wanted to sum up a list of objects and return an object, not an int. Using the class above, it would look like this:

class T:
    def __init__(self, x: int):
        self.x: int = x

    def __add__(self, other) -> "T":
        if isinstance(other, T):
            return T(other.x + self.x)
        else:
            return NotImplemented

    def __radd__(self, other) -> "T":
        return self.__add__(other)

However, if you call sum() over a list of these objects:

sum([T(1), T(2)])

you will get an error similar to:

TypeError: unsupported operand type(s) for +: 'int' and 'T'

This is because sum starts with 0, and incrementally adds to that number. Adding 0 to T doesn't work. Instead, starting from Python3.8, you can set the start to be T(0), and then your summation will work:

sum([T(1), T(2)], start=T(0))

Which returns an object of class T where the attribute x = 3.

Documentation for sum: https://docs.python.org/3/library/functions.html#sum

CREDIT: Not sure how to properly credit comments, but I learned about the start= from this answer https://mcmap.net/q/414061/-python-39-s-sum-and-non-integer-values on the comment from Max https://stackoverflow.com/users/4357999/max, thank you! I really just wanted to have this as a separate answer to bring visibility to this particular use case, since SEO does not rank the comment when searching for this answer and I almost missed it myself even when reading through the solutions.

Unmindful answered 6/7, 2023 at 18:57 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.