Python's Assignment Operator: Write Robust Assignments

Python's Assignment Operator: Write Robust Assignments

Python’s assignment operators allow you to define assignment statements. This type of statement lets you create, initialize, and update variables throughout your code. Variables are a fundamental cornerstone in every piece of code, and assignment statements give you complete control over variable creation and mutation.

Learning about the Python assignment operator and its use for writing assignment statements will arm you with powerful tools for writing better and more robust Python code.

In this tutorial, you’ll:

  • Use Python’s assignment operator to write assignment statements
  • Take advantage of augmented assignments in Python
  • Explore assignment variants, like assignment expressions and managed attributes
  • Become aware of illegal and dangerous assignments in Python

You’ll dive deep into Python’s assignment statements. To get the most out of this tutorial, you should be comfortable with several basic topics, including variables, built-in data types, comprehensions, functions, and Python keywords. Before diving into some of the later sections, you should also be familiar with intermediate topics, such as object-oriented programming, constants, imports, type hints, properties, descriptors, and decorators.

Assignment Statements and the Assignment Operator

One of the most powerful programming language features is the ability to create, access, and mutate variables. In Python, a variable is a name that refers to a concrete value or object, allowing you to reuse that value or object throughout your code.

The Assignment Statement Syntax

To create a new variable or to update the value of an existing one in Python, you’ll use an assignment statement. This statement has the following three components:

  1. A left operand, which must be a variable
  2. The assignment operator (=)
  3. A right operand, which can be a concrete value, an object, or an expression

Here’s how an assignment statement will generally look in Python:

Python
variable = expression

Here, variable represents a generic Python variable, while expression represents any Python object that you can provide as a concrete value—also known as a literal—or an expression that evaluates to a value.

To execute an assignment statement like the above, Python runs the following steps:

  1. Evaluate the right-hand expression to produce a concrete value or object. This value will live at a specific memory address in your computer.
  2. Store the object’s memory address in the left-hand variable. This step creates a new variable if the current one doesn’t already exist or updates the value of an existing variable.

The second step shows that variables work differently in Python than in other programming languages. In Python, variables aren’t containers for objects. Python variables point to a value or object through its memory address. They store memory addresses rather than objects.

This behavior difference directly impacts how data moves around in Python, which is always by reference. In most cases, this difference is irrelevant in your day-to-day coding, but it’s still good to know.

The Assignment Operator

The central component of an assignment statement is the assignment operator. This operator is represented by the = symbol, which separates two operands:

  1. A variable
  2. A value or an expression that evaluates to a concrete value

Operators are special symbols that perform mathematical, logical, and bitwise operations in a programming language. The objects (or object) on which an operator operates are called operands.

Unary operators, like the not Boolean operator, operate on a single object or operand, while binary operators act on two. That means the assignment operator is a binary operator.

The right-hand operand in an assignment statement can be any Python object, such as a number, list, string, dictionary, or even a user-defined object. It can also be an expression. In the end, expressions always evaluate to concrete objects, which is their return value.

Here are a few examples of assignments in Python:

Python
>>> number = 42
>>> greeting = "Hello, World!"

>>> total = 15 + 25
>>> is_true = 42 < 84

The first two sample assignments in this code snippet use concrete values, also known as literals, to create and initialize number and greeting. The third example assigns the result of a math expression to the total variable, while the last example uses a Boolean expression.

Unlike expressions, assignment statements don’t have a return value because their purpose is to make the association between the variable and its value. That’s why the Python interpreter doesn’t issue any output in the above examples.

Now that you know the basics of how to write an assignment statement, it’s time to tackle why you would want to use one.

Assignments and Variables

The assignment statement is the explicit way for you to associate a name with an object in Python. You can use this statement for two main purposes:

  1. Creating and initializing new variables
  2. Updating the values of existing variables

When you use a variable name as the left operand in an assignment statement for the first time, you’re creating a new variable. At the same time, you’re initializing the variable to point to the value of the right operand.

On the other hand, when you use an existing variable in a new assignment, you’re updating or mutating the variable’s value. Strictly speaking, every new assignment will make the variable refer to a new value and stop referring to the old one. Python will garbage-collect all the values that are no longer referenced by any existing variable.

Assignment statements not only assign a value to a variable but also determine the data type of the variable at hand. This additional behavior is another important detail to consider in this kind of statement.

Because Python is a dynamically typed language, successive assignments to a given variable can change the variable’s data type. Changing the data type of a variable during a program’s execution is considered bad practice and highly discouraged. It can lead to subtle bugs that can be difficult to track down.

Unlike in math equations, in Python assignments, the left operand must be a variable rather than an expression or a value. For example, the following construct is illegal, and Python flags it as invalid syntax:

Python
>>> a = 3
>>> b = 4
>>> hypotenuse ** 2 = a ** 2 + b ** 2
    ...
SyntaxError: cannot assign to expression here.
    Maybe you meant '==' instead of '='?

In this example, you have expressions on both sides of the = sign, and this isn’t allowed in Python code. The error message suggests that you may be confusing the equality operator with the assignment one, but that’s not the case. You’re really running an invalid assignment.

To correct this construct and convert it into a valid assignment, you’ll have to do something like the following:

Python
>>> from math import sqrt

>>> a = 3
>>> b = 4
>>> hypotenuse = sqrt(a ** 2 + b ** 2)
>>> hypotenuse
5.0

In this code snippet, you first import the sqrt() function from the math module. Then you isolate the hypotenuse variable in the original equation by using the sqrt() function. Now your code works correctly.

Now you know what kind of syntax is invalid. But don’t get the idea that assignment statements are rigid and inflexible. In fact, they offer lots of room for customization, as you’ll learn next.

Other Assignment Syntax

Python’s assignment statements are pretty flexible and versatile. You can write them in several ways, depending on your specific needs and preferences. Here’s a quick summary of the main ways to write assignments in Python:

Python
# Base assignments
variable = value  # A literal, such as None, 0, 3.14, "Hello", {}
variable = expression  # Math, Boolean, bitwise expression, function calls...

# Multiple assignments
variable_1 = variable_2 = ... = variable_n = expression

# Parallel assignments
variable_1, variable_2, ..., variable_n = value_1, value_2, ..., value_n
variable_1, variable_2, ..., variable_n = exp_1, exp_2, ..., exp_n

# Augmented assignments
existing_variable += value
existing_variable += expression

# Parallel assignments with iterable unpacking
variable_1, variable_2, ..., variable_n  = n_length_iterable
(variable_1, variable_2, ..., variable_n) = n_length_iterable
[variable_1, variable_2, ..., variable_n] = n_length_iterable
variable, *bag_variable, ..., variable_n = unknown_length_iterable

Up to this point, you’ve mostly learned about the base assignment syntax in the above code snippet. In the following sections, you’ll learn about multiple, parallel, and augmented assignments. You’ll also learn about assignments with iterable unpacking.

Read on to see the assignment statements in action!

Assignment Statements in Action

You’ll find and use assignment statements everywhere in your Python code. They’re a fundamental part of the language, providing an explicit way to create, initialize, and mutate variables.

You can use assignment statements with plain names, like number or counter. You can also use assignments in more complicated scenarios, such as with:

  • Qualified attribute names, like user.name
  • Indices and slices of mutable sequences, like a_list[i] and a_list[i:j]
  • Dictionary keys, like a_dict[key]

This list isn’t exhaustive. However, it gives you some idea of how flexible these statements are. You can even assign multiple values to an equal number of variables in a single line, commonly known as parallel assignment. Additionally, you can simultaneously assign the values in an iterable to a comma-separated group of variables in what’s known as an iterable unpacking operation.

In the following sections, you’ll dive deeper into all these topics and a few other exciting things that you can do with assignment statements in Python.

Initializing and Updating Variables

The most elementary use case of an assignment statement is to create a new variable and initialize it using a particular value or expression:

Python
>>> counter = 0
>>> celsius = 25
>>> fahrenheit = (celsius * 9 / 5) + 32
>>> user_template = {"id": None, "name": "", "permissions": ("r",)}
>>> welcome_message = "Welcome to Real Python!"
>>> is_empty = False

All these statements create new variables, assigning them initial values or expressions. For an initial value, you should always use the most sensible and least surprising value that you can think of. For example, initializing a counter to something different from 0 may be confusing and unexpected because counters almost always start having counted no objects.

Updating a variable’s current value or state is another common use case of assignment statements. In Python, assigning a new value to an existing variable doesn’t modify the variable’s current value. Instead, it causes the variable to refer to a different value. The previous value will be garbage-collected if no other variable refers to it.

Consider the following examples:

Python
>>> greeting = "Hello, World!"
>>> greeting
'Hello, World!'

>>> greeting = "Hi, Pythonistas!"
>>> greeting
'Hi, Pythonistas!'

These examples run two consecutive assignments on the same variable. The first one assigns the string "Hello, World!" to a new variable named greeting.

The second assignment updates the value of greeting by reassigning it the "Hi, Pythonistas!" string. In this example, the original value of greeting —the "Hello, World!" string— is lost and garbage-collected. From this point on, you can’t access the old "Hello, World!" string.

Even though running multiple assignments on the same variable during a program’s execution is common practice, you should use this feature with caution. Changing the value of a variable can make your code difficult to read, understand, and debug. To comprehend the code fully, you’ll have to remember all the places where the variable was changed and the sequential order of those changes.

Because assignments also define the data type of their target variables, it’s also possible for your code to accidentally change the type of a given variable at runtime. A change like this can lead to breaking errors, like AttributeError exceptions. Remember that strings don’t have the same methods and attributes as lists or dictionaries, for example.

Making Multiple Variables Refer to the Same Object

In Python, you can make several variables reference the same object in a multiple-assignment line. This can be useful when you want to initialize several similar variables using the same initial value:

Python
>>> letter_counter = word_counter = 0
>>> id(letter_counter) == id(word_counter)
True

In this example, you chain two assignment operators in a single line. This way, your two variables refer to the same initial value of 0. Note how both variables hold the same memory address, so they point to the same instance of 0.

When it comes to integer variables, Python exhibits a curious behavior. It provides a numeric interval where multiple assignments behave the same as independent assignments. Consider the following examples:

Python
>>> # Independent assignments
>>> n = 42
>>> m = 42
>>> id(n) == id(m)
True

>>> # Multiple assignments
>>> x = y = 42
>>> id(x) == id(y)
True

To create n and m, you use independent assignments. Therefore, they should point to different instances of the number 42. However, both variables hold the same object, which you confirm by comparing their corresponding memory addresses.

Now check what happens when you use a greater initial value:

Python
>>> n = 300
>>> m = 300
>>> id(x) == id(y)
False

>>> x = y = 300
>>> id(x) == id(y)
True

Now n and m hold different memory addresses, which means they point to different instances of the integer number 300. In contrast, when you use multiple assignments, both variables refer to the same object. This tiny difference can save you small bits of memory if you frequently initialize integer variables in your code.

The implicit behavior of making independent assignments point to the same integer number is actually an optimization called interning. It consists of globally caching the most commonly used integer values in day-to-day programming.

Under the hood, Python defines a numeric interval in which interning takes place. That’s the interning interval for integer numbers. You can determine this interval using a small script like the following:

Python
# interning.py

from platform import python_version

interning = [
    x
    for x, y in zip(range(-10, 500), range(-10, 500))
    if x is y
]

print(
    f"Interning interval for Python {python_version()} is:"
    f" [{interning[0]} to {interning[-1]}]"
)

This script helps you determine the interning interval by comparing integer numbers from -10 to 500. If you run the script from your command line, then you’ll get an output like the following:

Shell
$ python interning.py
Interning interval for Python 3.11.0 is: (-5 to 256)

This output means that if you use a single number between -5 and 256 to initialize several variables in independent statements, then all these variables will point to the same object, which will help you save small bits of memory in your code.

In contrast, if you use a number that falls outside of the interning interval, then your variables will point to different objects instead. Each of these objects will occupy a different memory spot.

Updating Lists Through Indices and Slices

You can use the assignment operator to mutate the value stored at a given index in a Python list. The operator also works with list slices. The syntax to write these types of assignment statements is the following:

Python
a_list[index] = expression

a_list[start:stop:step] = expression

In the first construct, expression can return any Python object, including another list. In the second construct, expression must return a series of values as a list, tuple, or any other sequence. You’ll get a TypeError if expression returns a single value.

Here’s an example of updating an individual value in a list:

Python
>>> numbers = [1, 2, 7, 4, 5]
>>> numbers
[1, 2, 7, 4, 5]

>>> numbers[2] = 3
>>> numbers
[1, 2, 3, 4, 5]

In this example, you update the value at index 2 using an assignment statement. The original number at that index was 7, and after the assignment, the number is 3.

It’s important to note that you can’t add new values to a list by using indices that don’t exist in the target list:

Python
>>> numbers[5] = 6
Traceback (most recent call last):
    ...
IndexError: list assignment index out of range

In this example, you try to add a new value to the end of numbers by using an index that doesn’t exist. This assignment isn’t allowed because there’s no way to guarantee that new indices will be consecutive. If you ever want to add a single value to the end of a list, then use the .append() method.

If you want to update several consecutive values in a list, then you can use slicing and an assignment statement:

Python
>>> letters = ["A", "b", "c", "D"]
>>> letters[1:3] = ["B", "C"]
>>> letters
['A', 'B', 'C', 'D']

>>> letters[3:] = ("F", "G")
>>> letters
['A', 'B', 'C', 'F', 'G']

>>> letters[3:3] = ["D"]
>>> letters
['A', 'B', 'C', 'D', 'F', 'G']

>>> letters[1::2] = ["b", "d", "g"]
>>> letters
['A', 'b', 'C', 'd', 'F', 'g']

In the first example, you update the letters between indices 1 and 3 without including the letter at 3. The second example updates the letters from index 3 until the end of the list. Note that this slicing appends a new value to the list because the target slice is shorter than the assigned values.

Also note that the new values were provided through a tuple, which means that this type of assignment allows you to use other types of sequences to update your target list.

The third example updates a single value using a slice where both indices are equal. In this example, the assignment inserts a new item into your target list.

In the final example, you use a step of 2 to replace alternating letters with their lowercase counterparts. This slicing starts at index 1 and runs through the whole list, stepping by two items each time.

Adding and Updating Dictionary Keys

Updating the value of an existing key or adding new key-value pairs to a dictionary is another common use case of assignment statements. To do these operations, you can use the following syntax:

Python
a_dict[existing_key] = expression

a_dict[new_key] = expression

The first construct helps you update the current value of an existing key, while the second construct allows you to add a new key-value pair to the dictionary.

For example, to update an existing key, you can do something like this:

Python
>>> inventory = {"apple": 100, "orange": 80, "banana": 120}
>>> inventory
{'apple': 100, 'orange': 80, 'banana': 120}

>>> inventory["orange"] = 140
>>> inventory
{'apple': 100, 'orange': 140, 'banana': 120}

In this example, you update the current inventory of oranges in your store using an assignment. The left operand is the existing dictionary key, and the right operand is the desired new value.

While you can’t add new values to a list by assignment, dictionaries do allow you to add new key-value pairs using the assignment operator. In the example below, you add a lemon key to inventory:

Python
>>> inventory["lemon"] = 100
>>> inventory
{'apple': 100, 'orange': 140, 'banana': 120, 'lemon': 100}

In this example, you successfully add a new key-value pair to your inventory with 100 units. This addition is possible because dictionaries don’t have consecutive indices but unique keys, which are safe to add by assignment.

Doing Parallel Assignments

The assignment statement does more than assign the result of a single expression to a single variable. It can also cope nicely with assigning multiple values to multiple variables simultaneously in what’s known as a parallel assignment.

Here’s the general syntax for parallel assignments in Python:

Python
variable_1, variable_2, ..., variable_n = value_1, value_2, ..., value_n

variable_1, variable_2, ..., variable_n = exp_1, exp_2, ..., exp_n

(variable_1, variable_2, ..., variable_n) = exp_1, exp_2, ..., exp_n

[variable_1, variable_2, ..., variable_n] = exp_1, exp_2, ..., exp_n

Note that the left side of the statement can be either a tuple or a list of variables. Remember that to create a tuple, you just need a series of comma-separated elements. In this case, these elements must be variables.

The right side of the statement must be a sequence or iterable of values or expressions. In any case, the number of elements in the right operand must match the number of variables on the left. Otherwise, you’ll get a ValueError exception.

In the following example, you compute the two solutions of a quadratic equation using a parallel assignment:

Python
>>> from math import sqrt

>>> a, b, c = 2.0, -1.0, -4.0

>>> x1, x2 = (
...     (-b - sqrt(b**2 - 4 * a * c)) / (2 * a),
...     (-b + sqrt(b**2 - 4 * a * c)) / (2 * a),
... )

>>> f"{x1=}, {x2=}"
'x1=-1.1861406616345072, x2=1.6861406616345072'

In this example, you first import sqrt() from the math module. Then you initialize the equation’s coefficients in a parallel assignment.

The equation’s solution is computed in another parallel assignment. The left operand contains a tuple of two variables, x1 and x2. The right operand consists of a tuple of expressions that compute the solutions for the equation. Note how each result is assigned to each variable by position.

A classical use case of parallel assignment is to swap values between variables:

Python
>>> previous_value = 42
>>> next_value = 43

>>> next_value, previous_value = previous_value, next_value

>>> previous_value
43
>>> next_value
42

The highlighted line does the magic and swaps the values of previous_value and next_value at the same time. Note that in a programming language that doesn’t support this kind of assignment, you’d have to use a temporary variable to produce the same effect:

Python
>>> previous_value = 42
>>> next_value = 43

>>> temp = previous_value
>>> previous_value = next_value
>>> next_value = temp

>>> previous_value
43
>>> next_value
42

In this example, instead of using parallel assignment to swap values between variables, you use a new variable to temporarily store the value of previous_value to avoid losing its reference.

For a concrete example of when you’d need to swap values between variables, say you’re learning how to implement the bubble sort algorithm, and you come up with the following function:

Python
>>> def bubble_sort_list(a_list):
...     n = len(a_list)
...     for i in range(n):
...         is_sorted = True
...         for j in range(n - i - 1):
...             if a_list[j] > a_list[j + 1]:
...                 a_list[j], a_list[j + 1] = a_list[j + 1], a_list[j]
...                 is_sorted = False
...         if is_sorted:
...             break
...     return a_list
...

>>> bubble_sort_list([1, 3, 2, 4, 7, 6, 3, 8, 9, 1])
[1, 1, 2, 3, 3, 4, 6, 7, 8, 9]

In the highlighted line, you use a parallel assignment to swap values in place if the current value is less than the next value in the input list. To dive deeper into the bubble sort algorithm and into sorting algorithms in general, check out Sorting Algorithms in Python.

Unpacking Iterables

You can use assignment statements for iterable unpacking in Python. Unpacking an iterable means assigning its values to a series of variables one by one. The iterable must be the right operand in the assignment, while the variables must be the left operand.

Like in parallel assignments, the variables must come as a tuple or list. The number of variables must match the number of values in the iterable. Alternatively, you can use the unpacking operator (*) to grab several values in a variable if the number of variables doesn’t match the iterable length.

Here’s the general syntax for iterable unpacking in Python:

Python
variable_1, variable_2, ..., variable_n  = n_length_iterable

(variable_1, variable_2, ..., variable_n) = n_length_iterable

[variable_1, variable_2, ..., variable_n] = n_length_iterable

variable, *bag_variable, ..., variable_n = unknown_length_iterable

Iterable unpacking is a powerful feature that you can use all around your code. It can help you write more readable and concise code. For example, you may find yourself doing something like this:

Python
>>> numbers = [1, 2, 3, 4]

>>> one = numbers[0]
>>> two = numbers[1]
>>> three = numbers[2]
>>> four = numbers[3]

>>> one
1
>>> two
2
>>> three
3
>>> four
4

Whenever you do something like this in your code, go ahead and replace it with a more readable iterable unpacking using a single and elegant assignment, like in the following code snippet:

Python
>>> numbers = [1, 2, 3, 4]

>>> one, two, three, four = numbers

>>> one
1
>>> two
2
>>> three
3
>>> four
4

The numbers list on the right side contains four values. The assignment operator unpacks these values into the four variables on the left side of the statement. The values in numbers get assigned to variables in the same order that they appear in the iterable. The assignment is done by position.

The above example shows the most common form of iterable unpacking in Python. The main condition for the example to work is that the number of variables matches the number of values in the iterable.

What if you don’t know the iterable length upfront? Will the unpacking work? It’ll work if you use the * operator to pack several values into one of your target variables.

For example, say that you want to unpack the first and second values in numbers into two different variables. Additionally, you would like to pack the rest of the values in a single variable conveniently called rest. In this case, you can use the unpacking operator like in the following code:

Python
>>> first, second, *rest = numbers
>>> first
1
>>> second
2
>>> rest
[3, 4]

In this example, first and second hold the first and second values in numbers, respectively. These values are assigned by position. The * operator packs all the remaining values in the input iterable into rest.

The unpacking operator (*) can appear at any position in your series of target variables. However, you can only use one instance of the operator:

Python
>>> *head, last = numbers
>>> head
[1, 2, 3]
>>> last
4

>>> head, *body, tail = numbers
>>> head
1
>>> body
[2, 3]
>>> tail
4

>>> *head, *rest = numbers
    ...
SyntaxError: multiple starred expressions in assignment

The iterable unpacking operator works in any position in your list of variables. Note that you can only use one unpacking operator per assignment. Using more than one unpacking operator isn’t allowed and raises a SyntaxError.

Dropping away unwanted values from the iterable is a common use case for the iterable unpacking operator. Consider the following example:

Python
>>> *_, useful = numbers
>>> useful
4
>>> _
[1, 2, 3]

In Python, if you want to signal that a variable won’t be used, then you use an underscore (_) as the variable’s name. In this example, useful holds the only value that you need to use from the input iterable. The _ variable is a placeholder that guarantees that the unpacking works correctly. You won’t use the values that end up in this disposable variable.

The pattern used in the above example comes in handy when you have a function that returns multiple values, and you only need a few of these values in your code. The os.walk() function may provide a good example of this situation.

This function allows you to iterate over the content of a directory recursively. The function returns a generator object that yields three-item tuples. Each tuple contains the following items:

  • The path to the current directory as a string
  • The names of all the immediate subdirectories as a list of strings
  • The names of all the files in the current directory as a list of strings

Now say that you want to iterate over your home directory and list only the files. You can do something like this:

Python
>>> import os

>>> for content in os.walk("/path/to/your/home"):
...     *_, filenames = content
...     print(filenames)
...

This code will issue a long output depending on the current content of your home directory. Note that you need to provide a string with the path to your user folder for the example to work. The _ placeholder variable will hold the unwanted data.

In contrast, the filenames variable will hold the list of files in the current directory, which is the data that you need. The code will print the list of filenames. Go ahead and give it a try!

Providing Default Argument Values

The assignment operator also comes in handy when you need to provide default argument values in your functions and methods. Default argument values allow you to define functions that take arguments with sensible defaults. These defaults allow you to call the function with specific values or to simply rely on the defaults.

As an example, consider the following function:

Python
>>> def greet(name="World"):
...     print(f"Hello, {name}!")
...

This function takes one argument, called name. This argument has a sensible default value that’ll be used when you call the function without arguments. To provide this sensible default value, you use an assignment.

Here’s how the function works:

Python
>>> greet()
Hello, World!

>>> greet("Pythonista")
Hello, Pythonista!

If you don’t provide a name during the call to greet(), then the function uses the default value provided in the definition. If you provide a name, then the function uses it instead of the default one.

Up to this point, you’ve learned a lot about the Python assignment operator and how to use it for writing different types of assignment statements. In the following sections, you’ll dive into a great feature of assignment statements in Python. You’ll learn about augmented assignments.

Augmented Assignment Operators in Python

Python supports what are known as augmented assignments. An augmented assignment combines the assignment operator with another operator to make the statement more concise. Most Python math and bitwise operators have an augmented assignment variation that looks something like this:

Python
variable $= expression

Note that $ isn’t a valid Python operator. In this example, it’s a placeholder for a generic operator. This statement works as follows:

  1. Evaluate expression to produce a value.
  2. Run the operation defined by the operator that prefixes the = sign, using the previous value of variable and the return value of expression as operands.
  3. Assign the resulting value back to variable.

In practice, an augmented assignment like the above is equivalent to the following statement:

Python
variable = variable $ expression

As you can conclude, augmented assignments are syntactic sugar. They provide a shorthand notation for a specific and popular kind of assignment.

For example, say that you need to define a counter variable to count some stuff in your code. You can use the += operator to increment counter by 1 using the following code:

Python
>>> counter = 0
>>> counter += 1
>>> counter
1
>>> counter += 1
>>> counter
2

In this example, the += operator, known as augmented addition, adds 1 to the previous value in counter each time you run the statement counter += 1.

It’s important to note that unlike regular assignments, augmented assignments don’t create new variables. They only allow you to update existing variables. If you use an augmented assignment with an undefined variable, then you get a NameError:

Python
>>> x += 1
Traceback (most recent call last):
    ...
NameError: name 'x' is not defined

Python evaluates the right side of the statement before assigning the resulting value back to the target variable. In this specific example, when Python tries to compute x + 1, it finds that x isn’t defined.

Great! You now know that an augmented assignment consists of combining the assignment operator with another operator, like a math or bitwise operator. To continue this discussion, you’ll learn which math operators have an augmented variation in Python.

Augmented Mathematical Assignment Operators

An equation like x = x + b doesn’t make sense in math. But in programming, a statement like x = x + b is perfectly valid and can be extremely useful. It adds b to x and reassigns the result back to x.

As you already learned, Python provides an operator to shorten x = x + b. Yes, the += operator allows you to write x += b instead. Python also offers augmented assignment operators for most math operators. Here’s a summary:

Operator Description Example Equivalent
+= Adds the right operand to the left operand and stores the result in the left operand x += y x = x + y
-= Subtracts the right operand from the left operand and stores the result in the left operand x -= y x = x - y
*= Multiplies the right operand with the left operand and stores the result in the left operand x *= y x = x * y
/= Divides the left operand by the right operand and stores the result in the left operand x /= y x = x / y
//= Performs floor division of the left operand by the right operand and stores the result in the left operand x //= y x = x // y
%= Finds the remainder of dividing the left operand by the right operand and stores the result in the left operand x %= y x = x % y
**= Raises the left operand to the power of the right operand and stores the result in the left operand x **= y x = x ** y

The Example column provides generic examples of how to use the operators in actual code. Note that x must be previously defined for the operators to work correctly. On the other hand, y can be either a concrete value or an expression that returns a value.

To illustrate how augmented assignment operators work, say that you need to create a function that takes an iterable of numeric values and returns their sum. You can write this function like in the code below:

Python
>>> def sum_all(numbers):
...    total = 0
...    for number in numbers:
...        total += number  # Augmented addition
...    return total
...

>>> sum_all([1, 2, 3, 4])
10

In this function, you first initialize total to 0. In each iteration, the loop adds a new number to total using the augmented addition operator (+=). When the loop terminates, total holds the sum of all the input numbers. Variables like total are known as accumulators. The += operator is typically used to update accumulators.

Another interesting example of using an augmented assignment is when you need to implement a countdown while loop to reverse an iterable. In this case, you can use the -= operator:

Python
>>> def custom_reversed(sequence):
...     index = len(sequence) - 1
...     while index >= 0:
...         yield sequence[index]
...         index -= 1
...

>>> list(custom_reversed("12345"))
['5', '4', '3', '2', '1']

In this example, custom_reversed() is a generator function because it uses yield. Calling the function creates an iterator that yields items from the input iterable in reverse order. To decrement the control variable, index, you use an augmented subtraction statement that subtracts 1 from the variable in every iteration.

Finally, counters are a special type of accumulators that allow you to count objects. Here’s an example of a letter counter:

Python
>>> word = "mississippi"
>>> counter = {}

>>> for letter in word:
...     if letter not in counter:
...         counter[letter] = 0
...     counter[letter] += 1
...

>>> counter
{'m': 1, 'i': 4, 's': 4, 'p': 2}

To create this counter, you use a Python dictionary. The keys store the letters. The values store the counts. Again, to increment the counter, you use an augmented addition.

Counters are so common in programming that Python provides a tool specially designed to facilitate the task of counting. Check out Python’s Counter: The Pythonic Way to Count Objects for a complete guide on how to use this tool.

Augmented Assignments for Concatenation and Repetition

The += and *= augmented assignment operators also work with sequences, such as lists, tuples, and strings. The += operator performs augmented concatenations, while the *= operator performs augmented repetition.

These operators behave differently with mutable and immutable data types:

Operator Description Example
+= Runs an augmented concatenation operation on the target sequence. Mutable sequences are updated in place. If the sequence is immutable, then a new sequence is created and assigned back to the target name. seq_1 += seq_2
*= Adds seq to itself n times. Mutable sequences are updated in place. If the sequence is immutable, then a new sequence is created and assigned back to the target name. seq *= n

Note that the augmented concatenation operator operates on two sequences, while the augmented repetition operator works on a sequence and an integer number.

Consider the following examples and pay attention to the result of calling the id() function:

Python
>>> # Mutable target
>>> list_1 = [1, 2, 3]
>>> id(list_1)
4323479104

>>> list_2 = [4, 5]
>>> list_2
[4, 5]

>>> list_1 += list_2  # Concatenation...
>>> list_1
[1, 2, 3, 4, 5]
>>> id(list_1)
4323479104

>>> # Immutable target
>>> tuple_1 = (1, 2, 3)
>>> id(tuple_1)
4387336896

>>> tuple_2 = (4, 5)
>>> tuple_2
(4, 5)

>>> tuple_1 += tuple_2  # Concatenation...
>>> tuple_1
(1, 2, 3, 4, 5)
>>> id(tuple_1)
4387485104

Mutable sequences like lists support the += augmented assignment operator through the .__iadd__() method, which performs an in-place addition. This method mutates the underlying list, appending new values to its end.

Immutable sequences, such as tuples and strings, don’t provide an .__iadd__() method. Therefore, augmented concatenations fall back to the .__add__() method, which doesn’t modify the sequence in place but returns a new sequence.

There’s another difference between mutable and immutable sequences when you use them in an augmented concatenation. Consider the following examples:

Python
>>> a_list = [1, 2, 3]
>>> a_list += (4, 5)
>>> a_list
[1, 2, 3, 4, 5]

>>> a_list += "67"
>>> a_list
[1, 2, 3, 4, 5, '6', '7']

>>> a_tuple = (1, 2, 3)
>>> a_tuple += [4, 5]
Traceback (most recent call last):
    ...
TypeError: can only concatenate tuple (not "list") to tuple

>>> a_string = "123"
>>> a_string += ("4", "5")
Traceback (most recent call last):
    ...
TypeError: can only concatenate str (not "tuple") to str

With mutable sequences, the data to be concatenated can come as a list, tuple, string, or any other iterable. In contrast, with immutable sequences, the data can only come as objects of the same type. You can concatenate tuples to tuples and strings to strings, for example.

Again, the augmented repetition operator works with a sequence on the left side of the operator and an integer on the right side. This integer value represents the number of repetitions to get in the resulting sequence:

Python
>>> # Mutable target
>>> a_list = [1, 2, 3]
>>> id(a_list)
4395563840

>>> n = 2
>>> a_list *= n  # Repetition...
>>> a_list
[1, 2, 3, 1, 2, 3]
>>> id(a_list)
4395563840

>>> a_list[0] is a_list[3]
True

>>> # Immutable target
>>> a_tuple = (1, 2, 3)
>>> id(a_tuple)
4393522944

>>> n = 2
>>> a_tuple *= n  # Repetition...
>>> a_tuple
(1, 2, 3, 1, 2, 3)
>>> id(a_tuple)
4394199328

>>> a_tuple[0] is a_tuple[3]
True

When the *= operator operates on a mutable sequence, it falls back to the .__imul__() method, which performs the operation in place, modifying the underlying sequence. In contrast, if *= operates on an immutable sequence, then .__mul__() is called, returning a new sequence of the same type.

Note that a_list[0] is a_list[3] returns True. This is because the *= operator doesn’t make a copy of the repeated data. It only reflects the data. This behavior can be a source of issues when you use the operator with mutable values.

For example, say that you want to create a list of lists to represent a matrix, and you need to initialize the list with n empty lists, like in the following code:

Python
>>> n = 3
>>> matrix = [[]]
>>> matrix *= n
>>> matrix
[[], [], []]

In this example, you use the *= operator to populate matrix with three empty lists. Now check out what happens when you try to populate the first sublist in matrix:

Python
>>> matrix[0].append(1)
>>> matrix[0].append(2)
>>> matrix[0].append(3)
>>> matrix
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]

The appended values are reflected in the three sublists. This happens because the *= operator doesn’t make copies of the data that you want to repeat. It only reflects the data. Therefore, every sublist in matrix points to the same object and memory address.

If you ever need to initialize a list with a bunch of empty sublists, then use a list comprehension:

Python
>>> n = 3
>>> matrix = [[] for _ in range(n)]
>>> matrix
[[], [], []]

>>> matrix[0].append(1)
>>> matrix[0].append(2)
>>> matrix[0].append(3)
>>> matrix
[[1, 2, 3], [], []]

This time, when you populate the first sublist of matrix, your changes aren’t propagated to the other sublists. This is because all the sublists are different objects that live in different memory addresses.

Augmented Bitwise Assignment Operators

Bitwise operators also have their augmented versions. The logic behind them is similar to that of the math operators. The following table summarizes the augmented bitwise operators that Python provides:

Operator Operation Example Equivalent
&= Augmented bitwise AND (conjunction) x &= y x = x & y
|= Augmented bitwise OR (disjunction) x |= y x = x | y
^= Augmented bitwise XOR (exclusive disjunction) x ^= y x = x ^ y
>>= Augmented bitwise right shift x >>= y x = x >> y
<<= Augmented bitwise left shift x <<= y x = x << y

The augmented bitwise assignment operators perform the intended operation by taking the current value of the left operand as a starting point for the computation. Consider the following example, which uses the & and &= operators:

Python
>>> number_1 = 123
>>> bin(number_1)
'0b1111011'

>>> number_2 = 456
>>> bin(number_2)
'0b111001000'

>>> # Bitwise AND
>>> #     0b1111011 (int 123)
>>> # & 0b111001000 (int 456)
>>> # -------------
>>> #     0b1001000 (int 72)

>>> number_1 & number_2
72
>>> bin(number_1 & number_2)
'0b1001000'

>>> number_1 &= number_2
>>> number_1
72
>>> bin(number_1)
'0b1001000'