Unpacking allows us to conveniently extract elements from a data structure (like a list, dictionary, tuple, etc) and assign them to variables.It offers a convenient way to assign multiple values to multiple variables at once. Consider the following examples:

without unpacking

mylist = [10, 20]

a = mylist[0]
b = mylist[1]
print(a)
print(b)

using unpacking

mylist = [10, 20]

a, b = mylist
print(a)
print(b)

As shown above, unpacking makes it easier to simultaneously assign multiple values to multiple variables. The elements in the target iterable object are assigned to the variables, based on their position, for example in the above example, the first element in the list is assigned to the first variable(a), the second element to the second variable(b), and so on

Note that, the number of variables on the left side of the assignment operator must match the number of elements present in the iterable being unpacked, otherwise a ValueError exception will be raised.

#too few elements in the iterable
a, b, c = (10, 20)
#too many elements in the iterable
a, b = (10, 20, 30)

Unpacking operators

Unpacking operators offers a lot of  flexibility on where and how the unpacking operations can be performed.There are two basic unpacking operators:

  1. The single asterisk (*) operator - for basic iterables, lists, tuples, etc.
  2. The double asterisk (**) operator - for mappings/dictionaries.

The single asterisk (*) operator

The single asterisk operator (*) is used to unpack an iterable such as a list, tuple e.tc into individual variables.It can be used to unpack iterable values that haven't been pre-assigned to individual variables. consider the following example.

mylist = [1, 2, 3, 4, 5]

*a, b, c = mylist
print(a)
print(b)
print(c)

In the above example, the variable a is assigned all elements of the list excluding the last two elements which are assigned to b and c. When the asterisk operator is encountered in a variable in  the right side of the assignment operator, depending on its position,  the remaining items in the list are assigned to the variable after all the items have been evaluated.  

a, *b, c = (1, 2, 3, 4, 5)
print(a)
print(b)
print(c)

In the above example, the variable preceded by an asterisk is which is in the middle of the variables, thus the first element in the tuple is assigned to a, the last one to c and all the ones appearing in between are assigned to b.

You can have only one asterisk unpacking operator in a single assignment statement and this is when you have a scenario where you want some individual variables and then "the rest".  Any more than one asterisk unpacking operator in an assignment statement will result in a SyntaxError exception.

*a, *b = (1, 2, 3, 4, 5)

Single asterisk unpacking in function calls

You can also unpack iterables in a function call using the single asterisk operator. This way, the contents of the iterable are passed as individual arguments to the function.

def add(a, b, c):
    print(f"{a} + {b} + {c} = {a + b + c}")

data = [20, 30, 50]
add(*data)

In the above example, we used the single asterisk operator so as to pass the values in the list as individual arguments to the add() function.  This is more convenient and faster than using indexing to access and pass each element of the list one by one. Without using unpacking, the above example would look as follows.

def add(a, b, c):
    print(f"{a} + {b} + {c} = {a + b + c}")

data = [20, 30, 50]
add(data[0], data[1], data[2])

Single asterisk unpacking for combining iterables

We can use the single asterisk operator to quickly combine multiple iterables into one larger iterable. This is more convenient than manually looping through the iterables.

tuple1 = (10, 20, 30)
tuple2 = (40, 50, 60)

mylist = [*tuple1, *tuple2]
print(mylist)

As shown above, the contents of each tuple is passed as an individual element in the list.

Double asterisks(**) operator

We have seen that the single asterisk operator(**) can be used to get individual elements from an iterable such as a tuple or list. However, if you use the operator with a dictionary or other mappings, it is only the keys that will be returned.

data = {'a': 10, 'b': 20, 'c': 30}

print(*data)

The double asterisk operator is used to unpack dictionaries including the values.This is useful especially when keyword arguments are used in function calls. We can pass the arguments through a mapping/dictionary like this: function(**dictionary)

def func(a, b, c):
    print(a)
    print(b)
    print(c)

data = {'c': 30, 'a': 10, 'b': 20}

#call the function 
func(**data)

When the  double unpacking operator is used to pass dictionary items as arguments in a function call, it  unpacks the dictionary into individual key-value pairs, and then the arguments from the dictionary are passed to the function as keyword arguments.

Another important use of the double unpacking operator is when we want to create a mapping from another mapping. For example we can use the operator to populate a new dictionary with items from an existing dictionary.

dict1 = {'a': 2, 'b': 3, 'c': 4}
dict2 = {**dict1, 'd': 5}

print(dict2)

In the above example, all the items of dict1 are unpacked to dict2 

Conclusion:

  • Unpacking is a convenient way to extract items from an iterable object into variables.
  • Unpacking can be used in assignment operation, in order to  assign multiple values to multiple variables at once.
  • The single asterisk operator (*) is used to unpack basic iterables such as lists and tuples.
  • The double asterisks operator is used to unpack mappings with key-value pairs.