Is i = i + n truly the same as i += n?

PythonPython 3.xOperator Overloading

Python Problem Overview


One block of code works but the other does not. Which would make sense except the second block is the same as the first only with an operation written in shorthand. They are practically the same operation.

l = ['table']
i = []
Version 1
for n in l:
    i += n
print(i)

Output: ['t', 'a', 'b', 'l', 'e']

Version 2
for n in l:
    i = i + n
print(i)

Output:

> TypeError: can only concatenate list (not "str") to list


What is causing this strange error?

Python Solutions


Solution 1 - Python

They don't have to be the same.

Using the + operator calls the method __add__ while using the += operator calls __iadd__. It is completely up to the object in question what happens when one of these methods is called.

If you use x += y but x does not provide an __iadd__ method (or the method returns NotImplemented), __add__ is used as a fallback, meaning that x = x + y happens.

In the case of lists, using l += iterable actually extends the list l with the elements of iterable. In your case, every character from the string (which is an iterable) is appended during the extend operation.

Demo 1: using __iadd__

>>> l = []
>>> l += 'table'
>>> l
['t', 'a', 'b', 'l', 'e']

Demo 2: using extend does the same

>>> l = []
>>> l.extend('table')
>>> l
['t', 'a', 'b', 'l', 'e']

Demo 3: adding a list and a string raises a TypeError.

>>> l = []
>>> l = l + 'table'
[...]
TypeError: can only concatenate list (not "str") to list

Not using += gives you the TypeError here because only __iadd__ implements the extending behavior.

Demo 4: common pitfall: += does not build a new list. We can confirm this by checking for equal object identities with the is operator.

>>> l = []
>>> l_ref = l # another name for l, no data is copied here
>>> l += [1, 2, 3] # uses __iadd__, mutates l in-place
>>> l is l_ref # confirm that l and l_ref are names for the same object
True
>>> l
[1, 2, 3]
>>> l_ref # mutations are seen across all names
[1, 2, 3]

However, the l = l + iterable syntax does build a new list.

>>> l = []
>>> l_ref = l # another name for l, no data is copied here
>>> l = l + [1, 2, 3] # uses __add__, builds new list and reassigns name l
>>> l is l_ref # confirm that l and l_ref are names for different objects
False
>>> l
[1, 2, 3]
>>> l_ref
[]

In some cases, this can produce subtle bugs, because += mutates the original list, while
l = l + iterable builds a new list and reassigns the name l.

BONUS

Ned Batchelder's challenge to find this in the docs

Solution 2 - Python

If in the second case, you wrap a list around n to avoid errors:

for n in l:
    i = i + [n]
print(i)

you get

['table']

So they are different operations.

Solution 3 - Python

No.

7.2.1. Augmented assignment statements:

> An augmented assignment expression like x += 1 can be rewritten as x = x + 1 to achieve a similar, but not exactly equal effect. In the augmented version, x is only evaluated once. Also, when possible, the > actual operation is performed in-place, meaning that rather than > creating a new object and assigning that to the target, the old object > is modified instead.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLuke BakareView Question on Stackoverflow
Solution 1 - PythontimgebView Answer on Stackoverflow
Solution 2 - PythonJakeView Answer on Stackoverflow
Solution 3 - PythonmscView Answer on Stackoverflow