11 minutes read

Today, we will cover how to unpack something, and how one * and two ** asterisks will help us with this. The chances are you have encountered the asterisks in Python before. They usually perform mathematical operations, * stands for multiplication, and ** is used for exponentiation. However, these two operators, also known as starred expressions, have another application for iterable object unpacking. Let's discuss it at length.

What is unpacking?

Even though you may have never seen this term before, you have encountered packing. Take a look at the following example:

my_tuple = 'hello', 'world', 123, [1, 2, 3]

The values in the code snippet above are "packed" together in a tuple. The reverse operation is called unpacking, where each variable is assigned to the corresponding tuple item. The variables for unpacking are on the left side of the assignment operator =:

a, b, c, d = my_tuple
print(a)  # hello
print(b)  # world
print(c)  # 123
print(d)  # [1, 2, 3]

Remember that the number of variables on the left should be equal to the number of the elements on the right. Otherwise, it raises a ValueError:

a, b, c = 'hello', 'world', 123, [1, 2, 3]
# ValueError: too many values to unpack (expected 3)

a, b, c, d, e = 'hello', 'world', 123, [1, 2, 3]
# ValueError: not enough values to unpack (expected 5, got 4)

Packing and unpacking can be performed not only with tuples but with any other iterables: lists, sets, tuples, strings, and dictionaries.

You may be wondering, why asterisks? The answer is straightforward — they make our coding life a little bit easier. Let's have a look!

The single asterisk operator

The single asterisk operator unpacks all the iterable variables that haven't been assigned to any variable. Let's look at an example. Suppose you want to extract the first and the last item of a list without indexing:

start, *middle, end = [1, 2, 3, 4, 5]
print(start)   # 1
print(end)     # 5
print(middle)  # [2, 3, 4]

As you can see, the "unused" items have been automatically assigned to the variable marked by an asterisk. This snippet is similar to the one below:

my_list = [1, 2, 3, 4, 5]
start = my_list[0]
end = my_list[-1]
middle = my_list[1:len(my_list)-1]

print(start)   # 1
print(end)     # 5
print(middle)  # [2, 3, 4]

This trick also works with lists of two items:

start, *middle, end = [1, 2]
print(start)   # 1
print(end)     # 2
print(middle)  # []

In the example above, the middle variable ends up being an empty list. There are no values that can be assigned to it, as the first start variable is assigned to 1, and the end variable is assigned to the last item on the list, 2.

One more thing. If for some reason you want to unpack all of the items in an iterable into a single variable, you need to convert it to a list or a tuple first. A simple trailing comma will do the trick; it will make our variable a list that can take as many arguments as you want:

*my_range = range(5)
# SyntaxError: starred assignment target must be in a list or tuple

*my_range, = range(5) 
# [0, 1, 2, 3, 4]

The purpose of the single asterisk

Why do we need it? When working with data, you sometimes need to split a sequence into "first-rest" or "last-rest" pairs. For instance, your data can contain row numbers as the first element, so you would want to separate them into different data structures. Using the * operator keeps the code clean and compact. Compare the two snippets below:

first, rest = sequence[0], sequence[1:]  # using indexing
first, *rest = sequence                  # using unpacking

# you can do the same to obtain the last element and all others
rest, last = sequence[:-1], sequence[-1]
*rest, last = sequence

In the same way, you can unpack iterable items when passing them to a function. Suppose you have a list of arbitrary numbers and a function that takes three numbers as arguments and returns the result of their multiplication:

nums = [1, 2, 3]

def multiply(num_1, num_2, num_3):
    return num_1 * num_2 * num_3

Instead of having to manually pass each argument to a function using indexing, you can simply unpack them with * operator:

# use indexing 
multiply(nums[0], nums[1], nums[2])  # 6

# use unpacking
multiply(*nums)  # 6

Looks pretty neat, right? Remember that the number of arguments must still be equal to the number of elements in an iterable object.

As you may already know, Python includes some functions that can take an unlimited number of arguments. Take the built-in print() or zip() functions. Or any other functions that you decide to implement manually. Have a look at the following examples:

nums = [1, 2, 3]
print(*nums)  # 1 2 3

# the code above is equal to
print(nums[0], nums[1], nums[2])  # 1 2 3

We can also print the elements from several iterable objects:

nums = [1, 2, 3]
more_nums = [4, 5, 6, 7]
print(*nums, *more_nums) 
# 1 2 3 4 5 6 7

Moreover, we can join multiple lists together:

nums = [1, 2, 3]
more_nums = [4, 5, 6, 7]
all_nums = [*nums, *more_nums]
print(all_nums)
# [1, 2, 3, 4, 5, 6, 7]

Even though we have performed the above operations mostly on tuples and lists, you can also do the same with sets and strings. We've also mentioned unpacking dictionaries, haven't we? Unlike others, dictionaries are stored in key-value pairs. What happens if we try to unpack them with the * operator?

my_dict = {'apple': 1, 'banana': 2, 'pear': 3}
print(*my_dict)
# apple banana pear

Note how it prints only the keys of the dictionary without values.

To unpack dictionary keys, values, or key-value pairs, you can use the * operator together with dictionary operations dict.keys(), dict.values() and dict.items().

The double-asterisk operator

While the single-asterisk operator unpacks lists, tuples, strings, and sets, the double-asterisk operator can unpack dictionaries. Unfortunately, dictionaries cannot be unpacked in the same way as lists and tuples. The code below will result in an error:

my_dict = {'apple': 1, 'banana': 2, 'pear': 3}
start, **middle, end = my_dict
# SyntaxError: invalid syntax

Nevertheless, we can still use ** to do any dictionary operation.

Let's assume we want to define a function that will return the sum of dictionary values. We can do it like this:

my_dict = {'apple': 1, 'banana': 2, 'pear': 3}

def fruit_sum(apple, banana, pear):
  return apple + banana + pear

print(fruit_sum(**my_dict)) # 6

Keep in mind that on defining a function, the function arguments must have the same name as those in a dictionary.

Similarly, the double asterisk can merge several dictionaries:

dict_1 = {'a': 1, 'b': 2, 'c': 3}
dict_2 = {'one': 'two', 'three': 'four'}

dict_3 = {**dict_1, **dict_2}
# {'a': 1, 'b': 2, 'c': 3, 'one': 'two', 'three': 'four'}

You can merge not only multiple dictionaries but also add extra key-value pairs to the existing ones. For instance:

my_dict = {'apple': 1, 'banana': 2, 'pear': 3}
my_dict_updated = {**my_dict, 'strawberry': 4}
print(my_dict_updated)
# {'apple': 1, 'banana': 2, 'pear': 3, 'strawberry': 4}

Or even create a copy of a dictionary and update its values at the same time:

my_dict = {'apple': 1, 'banana': 2, 'pear': 3}
my_dict_copy = {**my_dict, 'apple': 100}
print(my_dict_copy)
# {'apple': 100, 'banana': 2, 'pear': 3}

Conсlusion

In this topic, we have covered a very powerful tool — the unpacking operators. Here are some crucial things for take-away:

  • A single asterisk * unpacks items from lists, tuples, sets, and strings;
  • A double asterisk ** unpacks dictionaries;
  • Using a single asterisk on a dictionary will unpack only dictionary keys;
  • You can pass all iterable items to a function with the help of unpacking operators;
  • You can merge and update iterables using the starred expressions.
103 learners liked this piece of theory. 1 didn't like it. What about you?
Report a typo