Fundamentals 14 min read

Why Python’s += Operator Isn’t Just Sugar: Mutable vs Immutable Secrets

Python’s augmented assignment operators like += look simple, but their behavior varies dramatically between mutable and immutable objects, affecting performance and causing surprising bugs; this article explains the underlying data model, demonstrates with tuples, lists, and mixed structures, and reveals the hidden mechanics behind in‑place and object‑creation operations.

Code Mala Tang
Code Mala Tang
Code Mala Tang
Why Python’s += Operator Isn’t Just Sugar: Mutable vs Immutable Secrets

If you have used Python, you have certainly seen the augmented assignment operators such as +=, -=, *= and /=. They are convenient shortcuts that let you replace my_variable = my_variable + 5 with the shorter, more readable my_variable += 5, which follows the Pythonic style.

For a long time this was treated as simple syntactic sugar—a tiny feature that makes life easier—and in most cases using it is correct.

However, beneath this simple appearance lies an interesting and crucial aspect of Python’s data model. The behavior of these operators is not always as straightforward as it seems. Understanding what really happens when you use += on lists and tuples can be the difference between efficient, error‑free code and confusing, slow code.

Let’s lift the veil and explore the deep connection between augmented assignment, object mutability, and Python’s most famous “puzzle”.

Huge Difference Caused by Mutability

To understand augmented assignment we first need to recall a core Python concept: the difference between mutable and immutable objects.

Immutable objects cannot be changed once created. Numbers ( int, float), strings ( str) and tuples ( tuple) are the most common immutable types.

Mutable objects can be changed in place without creating a new object. Lists ( list), dictionaries ( dict) and sets ( set) are typical examples.

This distinction is the key to solving the augmented‑assignment mystery. Let’s see how it works.

Augmented Assignment in Immutable Types: The “Create and Replace” Dance

When you use += on an immutable object, Python has no choice but to perform a “create and replace” operation. Because the original object cannot be modified, Python computes the new value, creates a brand‑new object to store it, and then rebinds the variable name to this new object.

Consider a tuple and use id() to observe the change in identity:

# 操作不可变的元组
my_tuple = (10, 20, 30)
print(f"初始元组: {my_tuple}")
print(f"初始 ID: {id(my_tuple)}")

# 现在,让我们 “添加” 到它
my_tuple += (40, 50)

print(f"最终元组: {my_tuple}")
print(f"最终 ID:   {id(my_tuple)}")

Notice the ID changed! The expression my_tuple += (40, 50) is functionally equivalent to my_tuple = my_tuple + (40, 50). It creates a new, larger tuple and discards the old one.

The same principle applies to strings and numbers. Repeated creation of new objects can affect performance, especially when concatenating large strings inside a loop (although CPython includes some optimizations that make string concatenation more efficient than the model suggests).

Augmented Assignment in Mutable Types: The In‑Place Advantage

Now let’s turn our attention to mutable objects such as list. Here things become interesting. Mutable objects can be changed in place, so Python does not need to create a new object.

Python objects can define special “dunder” methods to control their behavior. For += the special method is __iadd__ (in‑place add). If an object implements __iadd__, Python calls it when you use +=.

The list object implements this method, which is equivalent to the .extend() method and directly modifies the list.

Let’s run the id() test with a list:

# 操作可变的列表
my_list = [10, 20, 30]
print(f"初始列表: {my_list}")
print(f"初始 ID: {id(my_list)}")

# 使用 += 添加到它
my_list += [40, 50]

print(f"最终列表 +=: {my_list}")
print(f"最终 ID +=: {id(my_list)}")

# 使用 + 添加到它
my_list = my_list + [60, 70]
print(f"最终列表 +: {my_list}")
print(f"最终 ID +: {id(my_list)}")

The ID is the same! We did not create a new list, we modified the original one. This is much more efficient for large lists because we avoid allocating new memory and copying all existing elements.

Key difference: my_list = my_list + [60, 70] creates a new list . my_list += [40, 50] performs an in‑place modification of the existing list.

When performance is critical, the in‑place version ( +=) is almost always the correct choice for mutable sequences.

The Confusing Question: What Happens When Two Worlds Collide?

We now have two clear rules: += creates a new object for immutable types, and += modifies mutable types in place. What happens if a mutable object is contained inside an immutable object?

This leads us to a classic Python brain‑teaser. Consider a tuple that contains a list as one of its elements:

# 一个元组包含一个可变的列表
t = (1, 2, [30, 40])

The tuple t is immutable—you cannot assign to its elements (e.g., t[0] = 99 is illegal). However, the inner list is mutable; you can freely append elements, clear it, or change its items.

Now the key question: what happens when you run the following line?

t[2] += [50, 60]

Think about it.

We are trying to modify the list – that should succeed because lists support in‑place addition.

We are also trying to modify the tuple – the += implies assignment, and assigning to a tuple element ( t[2] = …) is illegal, so that part should fail.

So which outcome occurs? Does it work, or does it raise an error?

The answer, paradoxically, is both.

Let’s run the code and see the result:

t = (1, 2, [30, 40])
print(f"原始元组:{t}")

try:
    t[2] += [50, 60]
except TypeError as e:
    print(f"
发生错误:{e}")

print(f"
最终元组值:{t}")

The code raises a TypeError as expected because the tuple is immutable, but the final value of t shows that the inner list was successfully modified.

Unraveling the Mystery

This strange behavior occurs because the += operation is not atomic. It is a series of steps executed sequentially, and the exception happens in the middle.

For s[k] += v, Python performs the following steps:

Retrieve the object : Python first fetches the object at s[k]. In our example it obtains the list [30, 40] and calls it obj.

Perform the in‑place operation : Python then calls obj.__iadd__(v). Because obj is a list, this in‑place addition succeeds, turning the list into [30, 40, 50, 60].

Attempt reassignment : Finally Python tries to execute s[k] = obj. Here it attempts t[2] = [30, 40, 50, 60]. Since t is a tuple, this assignment is illegal and raises TypeError.

The exception aborts the whole process, but it is too late – the in‑place modification has already happened.

This edge case illustrates a beautiful yet slightly scary Python mechanism. It is not a bug; it is the logical result of the rules we established earlier.

Key Takeaways

+=

is not merely syntactic sugar; its behavior depends entirely on the object it operates on.

For immutable types , += creates a new object. It is equivalent to x = x + y.

For mutable types , += usually modifies the object in place, implemented via the special method __iadd__.

Avoid placing mutable objects inside tuples. When a tuple contains mutable elements, the tuple’s “immutable” contract becomes fuzzy, which can lead to the surprising behavior shown above. Consider using a different data structure if you need mutability.

Conclusion

Augmented assignment operators are powerful tools in Python; they are concise and provide significant performance advantages for mutable objects. However, their behavior is tightly linked to the concepts of mutability and Python’s special‑method architecture. By understanding that a += b can mean “create a new object” or “modify this object in place”, you gain deeper control over code performance and correctness.

PerformancepythonData ModelImmutablemutabilityaugmented assignmentin-place operation
Code Mala Tang
Written by

Code Mala Tang

Read source code together, write articles together, and enjoy spicy hot pot together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.