36

I am trying to understand how __add__ works:

class MyNum:
    def __init__(self,num):
        self.num=num
    def __add__(self,other):
        return MyNum(self.num+other.num)
    def __str__(self):
        return str(self.num)

If I put them in a list

d=[MyNum(i) for i in range(10)]

this works

t=MyNum(0)
for n in d:
    t=t+n
print t

But this does not:

print sum(d)
TypeError: unsupported operand type(s) for +: 'int' and 'instance'

What am I doing wrong? How can I get the sum() to work?

My problem is how to use the sum on a list of objects that support the __add__, need to keep it as generic as possible.

Neuron
  • 5,141
  • 5
  • 38
  • 59
Ηλίας
  • 2,560
  • 4
  • 30
  • 44

5 Answers5

90

You need to define __radd__ as well to get this to work.

__radd__ is reverse add. When Python tries to evaluate x + y it first attempts to call x.__add__(y). If this fails then it falls back to y.__radd__(x).

This allows you to override addition by only touching one class. Consider for example how Python would have to evaluate 0 + x. A call to 0.__add__(x) is attempted but int knows nothing about your class. You can't very well change the __add__ method in int, hence the need for __radd__. I suppose it is a form of dependency inversion.

As Steven pointed out, sum operates in place, but starts from 0. So the very first addition is the only one that would need to use __radd__. As a nice exercise you could check that this was the case!

Ngenator
  • 10,909
  • 4
  • 41
  • 46
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
16
>>> help(sum)
Help on built-in function sum in module __builtin__:

sum(...)
    sum(sequence[, start]) -> value

    Returns the sum of a sequence of numbers (NOT strings) plus the value
    of parameter 'start' (which defaults to 0).  When the sequence is
    empty, returns start.

In other words, provide a start value:

sum(d, MyNum(0))

Edit pasted from my below comment:

sum works with a default start value of the integer zero. Your MyNum class as written does not know how to add itself to integers. To solve this you have two options. Either you can provide a start value to sum that has the same type as you class, or you can implement __radd__, which Python calls when adding values of differing types (such as when the first value in d is added to the default start value of zero).

Steven Rumbalski
  • 44,786
  • 9
  • 89
  • 119
  • 1
    Whoever voted -1, care to say why? Answer quotes docs and is technically correct. – Steven Rumbalski Feb 22 '11 at 18:39
  • @hop: I tested this and it worked. Could you expand on your comment? – Steven Rumbalski Feb 22 '11 at 18:40
  • So sum only works on if you subclass int? d=[MyNum(i) for i in range(10)] then sum(0,d) fails – Ηλίας Feb 22 '11 at 18:44
  • 3
    @Ηλίας: `sum` works with a default start value of the integer zero. Your `MyNum` class as written does not know how to add itself to integers. To solve this you have two options. Either you can provide a start value to `sum` that is the same type of your class, or you can implement `__radd__`, which Python calls when adding values of differing types (such as when the first value in `d` is added to the starting value of zero). – Steven Rumbalski Feb 22 '11 at 18:50
1

I oppose relaying on sum() with a start point, the loop hole exposed below,

In [51]: x = sum(d, MyNum(2))

In [52]: x.num
Out[52]: 47

Wondering why you got 47 while you are expecting like …start from 2nd of MyNum() while leaving first and add them till end, so the expected result = 44 (sum(range(2,10))

The truth here is that 2 is not kept as start object/position but instead treated as an addition to the result

sum(range(10)) + 2

oops, link broken !!!!!!

Use radd

Here below the correct code. Also note the below

Python calls __radd__ only when the object on the right side of the + is your class instance eg: 2 + obj1

#!/usr/bin/env python

class MyNum:
    def __init__(self,num):
        self.num=num

    def __add__(self,other):
        return MyNum(self.num+other.num)

    def __radd__(self,other):
        return MyNum(self.num+other)

    def __str__(self):
        return str(self.num)

d=[MyNum(i) for i in range(10)]
print sum(d)    ## Prints 45
d=[MyNum(i) for i in range(2, 10)]
print sum(d)    ## Prints 44
print sum(d,MyNum(2))   ## Prints 46 - adding 2 to the last value (44+2)
Haroon Rashedu
  • 155
  • 1
  • 9
0
class MyNum:
    def __init__(self,num):
        self.num=num
    def __add__(self,other):
        return self.num += other.num
    def __str__(self):
        return str(self.num)

one = MyNum(1)
two = MyNum(2)

one + two

print(two.num)
Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
  • 1
    OP is trying to understand code, but you simply dumped code without any explanation. Please [edit] your answer to include a description of your code and actually answer the question. If you don't want that, you may delete your answer since it is entirely not useful. – Artjom B. Apr 12 '15 at 16:03
0

Another option is reduce (functools.reduce in Python 3.x).

from functools import reduce
from operators import add
d=[MyNum(i) for i in range(10)]
my_sum = reduce(add,d)
a3t
  • 1