Designing class with __add__

Hello Python Experts,

I have a collection of objects for which the sum operation makes sense. Therefore I do:

def __add__(self, obj):
    #something here that adds obj to self and creates val, an instance of the 
    #current class.
    return val

however I have a piece of code that looks like:

def get_total():
    l_obj = []
    for iobj in range(10):
        #get object here
        l_obj.append(obj)
 
    tot = sum(l_obj)
    return tot

Here sum adds to zero, the number. Thus, I should do something like:

def __add__(self, obj):
    if obj == 0:
         return self
    #something here that adds obj to self and creates val, an instance of the 
    #current class.
    return val

this should work, but is this recommended/sensible? I do want to use sum to avoid messy code.

Cheers.

The sum builtin accepts an optional start parameter, which defaults to zero. If you have an additive identtity for your class (a class-specific zero of some sort), you can pass that as the second argument:

sum(l_obj, start=Thing())

As a side note, names like l_obj don’t really carry a lot of information; every value in Python is an object of some sort, and all it’s saying is that this is some sort of list. You might not need any of that though; if the “get object here” part is reasonably compact, you could do the whole job in a list comprehension. Here’s an example using simple arithmetic:

def get_total():
    l_obj = []
    for iobj in range(10):
        obj = iobj * iobj
        l_obj.append(obj)
 
    tot = sum(l_obj)
    return tot

This could be implemented much more simply like this:

def get_total():
    return sum([i * i for i in range(10)])

Much nicer. :slight_smile:

2 Likes

Hello Chris,

Thank you for your answer. Yes, that’s right, the start is allowed, but the point is that the value is zero by default. In general, obj + 0 does not make sense unless you define __add__ in such a way that it just returns obj. Your suggestion of implementing an additive identity is interesting and could be done. However, wouldn’t it be easier to make 0 an additive identity with?

def __add__(self, obj):
    if obj == 0:
         return self
    #something here that adds obj to self and creates val, an instance of the 
    #current class.
    return val

It’s not rethoric though, I wonder if this is the best approach or if there is a good reason why implementing an additive (and in the long term multiplicative, etc) identity is a better idea. I just do not want to be stuck with a pile of badly designed code that I will have to rewrite at some point :slight_smile:

Yes, in my code I do not use l_obj but something more meaningful, here I wanted to be generic so I used obj.

I have a collection of objects for which the sum operation makes sense.
Therefore I do:

def __add__(self, obj):
   #something here that adds obj to self and creates val, an instance of the
   #current class.
   return val

Note that that only makes sense inside a class definition. Eg:

 class C:
     def __add__(self, obj):
         #something here that adds obj to self and creates val, an 
         nstance of the
         #current class.
         return val

Then if you make objects of type C (which isn’t complete yet) then you
can add them together.

however I have a piece of code that looks like:

def get_total():
   l_obj = []
   for iobj in range(10):
       #get object here
       l_obj.append(obj)
   tot = sum(l_obj)
   return tot

Here sum adds to zero, the number.

The builtin sum adds everything in its argument to 0 (or some other
starting value if you like). So, yes.

Thus, I should do something like:

def __add__(self, obj):
   if obj == 0:
        return self
   #something here that adds obj to self and creates val, an instance of the
   #current class.
   return val

This isn’t very different. All it has is a tiny optimisation to return
self if adding obj to it is like adding 0 to it i.e. do not return
a new object if that is the case.

But this assumes a fair bit about your class. You might be thinking in
terms of natural numbers, but maybe your objects are something else? You
haven’t said yet.

if you’re entirely new to implementing classes with operations like
__add__, I’d start with objects which just store a number because it
makes the addition easy to implement.

this should work, but is this recommended/sensible? I do want to use sum to avoid messy code.

Well, if you do it right, yes. sum() starts with a starting value, and
adds everything you give it to that. So the starting value wants an add
operation which works with the objects.

Let’s see what __add__ does, here:

Ignore the preview (which shows the top of the page), the link itself
goes directly to the __add__ method definition.

If you’ve got x+y, Python runs type(x).__add__(x,y). This means that
we’re calling x’s add operation to add x and y.

In order to use your __add__ operation, the left hand object needs
to be of your type. If you’ve got some class C and you’re using
sum(), that means you need to invoke sum() like this:

 sum(your_objects, start=c0)

which c0 is whatever the sum should start with. Maybe you can make a
“zero” object, eg:

 c0 = C(0)

if that makes sense for your objects, and use that.

Without the start=, the sum() function starts with 0, and uses the
int.__add__ operation to add up the objects.

Put some print() calls inside your __add__ method to see what
arguments it is getting, and to see if it is even being called :slight_smile:

1 Like

Why use an intermediate list at all?

def get_total():
    return sum(i * i for i in range(10))
1 Like

Because I was trying to keep it as close to the original as possible. Plus, if you add the start parameter, the benefit largely vanishes, as the genexp needs to be parenthesized.

The readability benefit, yes, but not the scalability benefit of learning to avoid intermediate lists.

Defining __add__ for the input 0, has as a consequence that now the arithmetic of your class has 0 as a valid operand. That means that the addition of 0 in sum works, but it would also work any unintended addition of 0 somewhere else. If addition of 0 makes sense in your case, in general, then that is fine. If not, it would be better that unintended occurrences of + 0 keep being an error.

You could define an instance of your class that is an additive neutral, but it is not necessary. You don’t necessarily need to define the empty sum. That is entirely up to you and your specific application. You could use the first element of the list as the value of start and the rest to be the first argument of sum.

1 Like