Unpacking Operators in Python. Using the * and ** unpacking operators... | by Luay Matalka | Towards Data Science

In this tutorial, we will learn how to use the asterisk (*) operator to unpack iterable objects, and two asterisks (**) to unpack dictionaries. In addition, we will discuss how we can pack several values into one variable using the same operator. And lastly, we will discuss what *args and **kwargs are and when they can be used.
Let’s say we have a list:
num_list = [1,2,3,4,5]
And we define a function that takes in 5 arguments and returns their sum:
def num_sum(num1,num2,num3,num4,num5):    return num1 + num2 + num3 + num4 + num5
And we want to find the sum of all the elements in num_list. Well, we can accomplish this by passing in all the elements of num_list to the function num_sum. Since num_list has five elements in it, the num_sum function contains five parameters, one for each element in num_list.
One way to do this would be to pass the elements by using their index as follows:
num_sum(num_list[0], num_list[1], num_list[2], num_list[3], num_list[4])# 15
However, there is a much easier way to do this, and that’s by using the * operator. The * operator is an unpacking operator that will unpack the values from any iterable object, such as lists, tuples, strings, etc...
For example, if we want to unpack num_list and pass in the 5 elements as separate arguments for the num_sum function, we could do so as follows:
num_sum(*num_list)# 15
And that’s it! The asterisk, *, or unpacking operator, unpacks num_list, and passes the values, or elements, of num_list as separate arguments to the num_sum function.
Note: For this to work, the number of elements in num_list must match the number of parameters in the num_sum function. If they don’t match, we would get a TypeError.
We can also use the asterisk, *, or unpacking operator, with built-in functions in python, such as print:
print(*num_list)# 1 2 3 4 5
Let’s say we have another list:
num_list_2 = [6,7,8,9,10]
And we want to print all the elements in both num_list and num_list_2. We can use the unpacking operator, *, to accomplish this as follows:
print(*num_list, *num_list_2)# 1 2 3 4 5 6 7 8 9 10
Both num_list and num_list_2 are unpacked. Then, all the elements are passed in to print as separate arguments.
We can also create a new list that contains all the elements from num_list and num_list_2:
new_list = [*num_list, *num_list_2]# [1,2,3,4,5,6,7,8,9,10]
num_list and num_list_2 are unpacked, resulting in their elements constituting the elements of the newly made list, new_list.
Note: We could have simply added num_list and num_list_2 to create new_list. However, this was just to portray the functionality of the unpacking operator.
towardsdatascience.com
Let’s say that we have a string assigned to the variable name:
name = ‘Michael’
And we want to break this name up into 3 parts, with the first letter being assigned to a variable, the last letter being assigned to another variable, and everything in the middle assigned to a third variable. We can do so as follows:
first, *middle, last = name
And that’s it! Since name is a string, and strings are iterable objects, we can unpack them. The values on the right side of the assignment operator will be assigned to the variables on the left depending on their relative position in the iterable object. As such, the first letter of ‘Michael’ is assigned to the variable first, which would be ‘M’ in this case. The last letter, ‘l’, is assigned to the variable last. And the variable middle will contain all the letters between ‘M’ and ‘l’ in the form of a list: [‘i’, ‘c’, ‘h’, ‘a’, ‘e’].
Note: The first and last variables above are called mandatory variables, as they must be assigned concrete values. The middle variable, due to using the * or unpacking operator, can have any number of values, including zero. If there are not enough values to unpack for the mandatory variables, we will get a ValueError.
For example, if we used the following assignment instead:
first, *middle, last = ‘ma'
Then the variable first will be assigned ‘m’, the variable last will be assigned ‘a’, and the variable middle will just be an empty list since there are no other values to assign to it.
towardsdatascience.com
We can also use the * operator to pack multiple values into a single variable. For example:
*names, = ‘Michael’, ‘John’, ‘Nancy’# names ['Michael', 'John', 'Nancy']
The reason for using a trailing comma after *names is because the left side of the assignment must be a tuple or list. Therefore, the names variable now contains all the names on the right side in the form of a list.
Note: This is what we do when we define functions that can receive a varying number of arguments! That is the concept of *args and **kwargs!
For example, let’s say we have a function, names_tuple, that takes in names as arguments and returns them back. However, the number of names that we pass in to this function can vary. Well, we can’t just choose a number of parameters that this function would have since the number of positional arguments can change with each calling of the function. We can instead use the * operator to pack the arguments passed in into a tuple as follows:
def names_tuple(*args):    return argsnames_tuple('Michael', 'John', 'Nancy')# ('Michael', 'John', 'Nancy')names_tuple('Jennifer', 'Nancy')# ('Jennifer', 'Nancy')
No matter what number of positional arguments we pass in when we call the names_tuple function, the *args argument will pack the positional arguments into a tuple, similar to the *names assignment above.
To pass in a varying number of keyword or named arguments, we use the ** operator when defining a function. The ** unpacking operator will pack the varying number of named arguments we pass in into a dictionary.
def names_dict(**kwargs):    return kwargsnames_dict(Jane = 'Doe')# {'Jane': 'Doe'}names_dict(Jane = 'Doe', John = 'Smith')# {'Jane': 'Doe', 'John': 'Smith'}
Note: When using the * operator to create a parameter that receives a varying number of positional arguments when defining a function, it is common to use the parameter name args (and kwargs to receive a varying number of keyword or named arguments). However, any names can be chosen for these parameters.
towardsdatascience.com
What happens when we try to use the * operator with a dictionary?
num_dict = {‘a’: 1, ‘b’: 2, ‘c’: 3}print(*num_dict)# a b c
Notice how it printed the keys of the dictionary and not the values? To unpack a dictionary, we need to use the ** unpacking operator. However, since each value is associated with a specific key, the function that we pass these arguments to must have parameters with the same names as the keys of the dictionary being unpacked. For example:
def dict_sum(a,b,c):    return a+b+c
This dict_sum function has three parameters: a, b, and c. These three parameters are named the same as the keys of num_dict. Therefore, once we pass in the unpacked dictionary using the ** operator, it’ll assign in the values of the keys according to the corresponding parameter names:
dict_sum(**num_dict)# 6
Thus, the values, or arguments, for the a, b, and c parameters in dict_sum will be 1, 2, and 3, respectively. And the sum of these three values is 6.
Just like with lists, the ** operator can be used to merge two or more dictionaries:
num_dict = {‘a’: 1, ‘b’: 2, ‘c’: 3}num_dict_2 = {‘d’: 4, ‘e’: 5, ‘f’: 6}new_dict = {**num_dict, **num_dict_2}# {‘a’: 1, ‘b’: 2, ‘c’: 3, ‘d’: 4, ‘e’: 5, ‘f’: 6}
towardsdatascience.com
In this tutorial, we learned how to use the * operator to unpack iterable objects and the ** operator to unpack dictionaries. We also learned many ways to utilize these operators to accomplish many different tasks. In addition, we briefly discussed the concept of packing with *args and **kwargs by using the * and ** operators when defining functions that receive a varying number of positional or named arguments.