The “+=” May Not Do What You Expected In Python



Original Source Here

The “+=” May Not Do What You Expected In Python

A discussion and comparison of 3 different “add” functions

Do you think a = a + b is always the same as a += b in Python? The answer is no. Like most of the other Python developers, I was thinking the same thing before, until I realised that there are 3 different “adding” functions in Python.

  • __add__()
  • __radd__()
  • __iadd__()

Don’t recognise them? Don’t worry. If you know that the functions surrounded by double underscores are “magic” functions, you should already realise that these functions should be called in more intuitive ways. Exactly, when we have a + b, the function a.__add__(b) is called behind the scene.

In this article, I’ll introduce all these 3 functions and the differences between them. More importantly, I’ll show you in which scenario a = a + b will not be the same as a += b, and why.

1. When __add__ is Called?

Image by Ulrike Leone from Pixabay

As mentioned in the introduction, the function __add__() is called whenever we are doing the SUM calculations. Therefore, we can say that the two expressions below are always the same.

a + b
a.__add__(b)

We can verify this by defining a custom class. Let’s call it class “X”. Then, the class “X” will take a number for initialisation. Also, we need to define the magic function __add__(). For convenience, the __repr__() function is also defined so that we can easily print the results.

class X:
def __init__(self, num):
self.num = num
def __add__(self, other_X):
print('I am called!')
return X(self.num + other_X.num)
def __repr__(self):
return str(self.num)

Now, let’s instantiate two numbers from this class “X”, and perform a sum calculation.

a = X(1)
b = X(2)
print('a + b =', a + b)

In the __add__() function, I’ve added a print function to print something. Then, when we run a + b, it was printed. So, it proved that we have successfully overwritten the + operator for this customised class.

2. What Makes __radd__ Different from __add__?

Image by yusuf kazancı from Pixabay

This function is a bit special. It will reverse the order of the two components and then add them. Well, we all know that a + b = b + a if both of them are scalar. Therefore, it will be much easier to demonstrate it using dividing rather than adding.

Fortunately, there are two corresponding functions __truediv__() and __rturediv(). See the examples below.

x = 10
y = 2
x.__truediv__(y)
x.__rtruediv__(y)

Therefore,

  • x.__truediv__(y) = x/y
  • x.__rtruediv__(y) = y/x

Similarly,

  • x.__add__(y) = x + y
  • x.__radd__(y) = y + x

If you’re interested in how many other operators or calculations having these features, here is a link to the official docs for you.

3. When __radd__ is Called?

Image by Circ OD from Pixabay

Now we understand that the expression a.__radd__(b) is equivalent to b + a. But hold on, when we have the expression b + a, it surely will run b.__add__(a) rather than the former. Then, when the function__radd__() will be called?

Remember we have defined a customised class “X” and we have to implement the __add__() function to enable the operator “+” to be used between the “X” instances? That means, sometimes we may not have it in a class.

Let’s define another class “Y” and make it identical with “X” but without overwriting the function __add__().

class Y:
def __init__(self, num):
self.num = num
def __repr__(self):
return str(self.num)

Now, we define a and b again but using X and Y respectively. Then, we try to run b + a.

a = X(1)
b = Y(2)
print('b + a =', b + a)

Because b is a type of Y but there is no __add__() implemented, there is no b.__add__(a) existing.

I hope so far you still understand. Then, let’s implement the __radd__() function in class X and then run it again.

In class X, add the following function:

def __radd__(self, other_num):
print('Reversed adding!')
return X(self.num + other_num.num)

Yes, your idea is correct. When an object does not have __add__() but the other object in the sum calculation have the __radd__() function implemented, this function will be called.

4. When __iadd__ is Called?

Image by Shutterbug75 from Pixabay

The shore answer is that the __iadd__() function will be called when we use the += operator. However, unlike the __radd__() function, even if we don’t implement the __iadd__() function, the “+=” operator can still work. It will just fall back to the __add__() function if there is one.

Let keep using the example defined in section 1.

class X:
def __init__(self, num):
self.num = num
def __add__(self, other_X):
print('I am called!')
return X(self.num + other_X.num)
def __repr__(self):
return str(self.num)
a = X(1)
b = X(2)
a += b
print('new a =', a)

Of course, if we have implemented the function __iadd__() and use the “+=” operator, it will be called.

Let’s prove that by implementing the __iadd__() function.

class X:
def __init__(self, num):
self.num = num
def __add__(self, other_X):
print('I am called!')
return X(self.num + other_X.num)
def __iadd__(self, other_X):
print('return myself')
self.num = self.num + other_X.num
return self
def __repr__(self):
return str(self.num)

5. The Difference Between “a=a+b” and “a+=b”

In the previous example, have you noticed that the difference in how I implement the __iadd__() function? Yes, rather than returning a new object as __add__() do, I make it return self. In other words, no new object is created in this operation.

This is also the official definition of __iadd__().

object.__iadd__(self, other)

These methods should attempt to do the operation in-place (modifying self) and return the result.

That will create the difference between the functions __add__() and __iadd__(), and consequently makes the operator “+” and “+=” different in some scenarios.

Don’t forget that we can use the “+” operator on any Python list, so let’s experiment on lists.

Firstly, let’s define two simple lists a and b.

a = [1, 2]
b = [3, 4]

Then, let’s define another variable c, assign a to it.

c = a

Can’t be simpler, right? 🙂

Now, let’s add a and b and assign it back to a.

a = a + b
print("a =", a)
print("c =", c)

The result shows that a and c are different. The list c still equals to the “original” a.

Now, let’s try the “+=” operator.

a = [1, 2]
b = [3, 4]
c = a
a += b
print("a =", a)
print("c =", c)

This time, the object c was changed because of a was modified!

This is exactly because of the feature of the function __iadd__(). It will modify the object in place rather than a new one, and this leads to the modification of the object c.

In fact, only mutable objects will be affected by this “feature”. If the objects we’re trying to sum are immutable, the “+” and “+=” can be considered as the same.

We can experiment on tuples because Python tuples are considered immutable objects.

a = (1, 2)
b = (3, 4)
c = a
a += b
print("a", a)
print("c", c)

This time, the object c wasn’t changed.

Perhaps one more example for integer, because we may always think the “+” and “+=” are the same is caused by we are more often adding numbers rather than lists. For example, integers in Python are immutable as well.

Therefore, it would be safe enough to use “+=” on numbers, don’t worry 🙂

Summary

Image by Gerd Altmann from Pixabay

In this article, I have introduced the three ways of adding objects in Python. They are “magic functions” which will be called when we use the operators. Now you should understand when they will be called and what features they will provide.

Most importantly, be aware of the difference between a = a + b and a += b. They will create different results when the objects are mutable.

Life is short, use Python!

AI/ML

Trending AI/ML Article Identified & Digested via Granola by Ramsey Elbasheer; a Machine-Driven RSS Bot

%d bloggers like this: