Value unpacking is a very useful feature in Python. It helps make the code readable and more chic 🙂
The idioms presented here are the most common ways to use unpacking in Python and are intended to help you understand how it works and when it can be useful.
These idioms work for iterables (lists, strings) and tuples.

1. Classical variable unpacking in Python

The one idiom you are probably familiar with from your python course is a classical unpacking idiom, where you assign each element of an iterable (or a tuple) to a specific variable:

>>> data = ("Elena", "nhoj546", "Mon, 7 Nov 2016 08:16:56 GMT", "elena@linuxnix.com", ("172.217.22.110", "http", "142.113.71.12"))
>>> username, passwd, last_login, email, connection_details = data
>>> username
'Elena'
>>> passwd
'nhoj546'
>>> connection_details
('172.217.22.110', 'http', '142.113.71.12')
>>> connection_details[1]
'http'
>>> ip_for, protocol, ip_by = connection_details
>>> protocol
'http'
>>>

2. Replace unnecessary values with "_"

If you do not want to litter the namespace with additional variables you can assign the elements that you do not need to an underscore (“_”) while unpacking. The items assigned to an underscore are dropped:

>>> user = "lena:x:1000:1000:lena user,,,:/home/lena:/bin/bash"
>>> username, _, id, _, fullname, homedir, shell = user.split(":")
>>> id
'1000'
>>> shell
'/bin/bash'
>>>

3.Wrapping several values into a list while unpacking

Note: available in Python3

This idiom allows you to unpack certain values and wrap others into a list.

a) Wrapping at the beginning of the iterable

>>> data = ("Elena", "nhoj546", "Mon, 7 Nov 2016 08:16:56 GMT", "elena@linuxnix.com", ("172.217.22.110", "http", "142.113.71.12"))
>>> *user_details, date, email, _ = data
>>> date
'Mon, 7 Nov 2016 08:16:56 GMT'
>>> email
'elena@linuxnix.com'
>>> user_details
['Elena', 'nhoj546']
>>>

The following assignment

 >>> *user_details, date, email, _ = data

picks exactly three elements at the end of the tuple, assigns the first two to variables and drops the third one. The rest of the tuple is stored in a newly created “user_details” list.

b) Wrapping at the end of the iterable

>>> data = ("Elena", "nhoj546", "Mon, 7 Nov 2016 08:16:56 GMT", "elena@linuxnix.com", ("172.217.22.110", "http", "142.113.71.12"))
>>> user, passwd, *info = data
>>> user
'Elena'
>>> info
['Mon, 7 Nov 2016 08:16:56 GMT', 'elena@linuxnix.com', ('172.217.22.110', 'http', '142.113.71.12')]
>>>

Here we assign the first two elements of the list to variables and shove the rest into the info list.

c) Wrapping in the middle of the iterable

>>> data = ("Elena", "nhoj546", "Mon, 7 Nov 2016 08:16:56 GMT", "elena@linuxnix.com", ("172.217.22.110", "http", "142.113.71.12"))
>>> name, *details, connection_info = data
>>> name
'Elena'
>>> details
['nhoj546', 'Mon, 7 Nov 2016 08:16:56 GMT', 'elena@linuxnix.com']
>>> connection_info
('172.217.22.110', 'http', '142.113.71.12')
>>>

In this case, we take the first and the last elements of the initial iterable and wrap the elements in the middle into a list.

We can go further and unpack the “connection_info” tuple:

>>> name, *details, (ip_for, protocol, ip_by) = data
>>> name
'Elena'
>>> details
['nhoj546', 'Mon, 7 Nov 2016 08:16:56 GMT', 'elena@linuxnix.com']
>>> ip_for
'172.217.22.110'
>>>

To imitate the above unpacking in python 2.x you can define a function (see the next section for more information on the * operator applied to function arguments):

>>> def func(arg1, arg2, *arg3):
            ... return arg1, arg2, arg3
            ...
>>> func("Elena", "Chloe", "Mike", "Tim", "Grace")
("Elena", "Chloe", ("Mike", "Tim", "Grace"))
>>>

However, this will work only for elements at the end of the iterable.

4. Functions with indefinite number of arguments

Sometimes you want to define a function which may take any number of arguments; this is generally the case when you are not sure how many arguments will be passed to your function. Python provides a convenient way of dealing with this issue: the * operator, which we have already seen in the the previous section:

>>> def add_users(first, *rest):
            ... users = {0: first}
            ... users.update(dict(zip(range(1, len(rest)+1), rest)))
            ... return users
            ...
>>> add_users("elena", "danny", "juliette")
{0: 'elena', 1: 'danny', 2: 'juliette'}
>>> add_users("elena")
{0: 'elena'}

However the behavior of the * operator is slightly different in case of function arguments: the * argument (*rest in our example) can only be the last positional argument in a function definition.

>>> def add_users(first, *rest, last):
            ... print(last)
            ...
>>> add_users("elena", "danny", "juliette")
Traceback (most recent call last):
File "", line 1, in
TypeError: add_users() missing 1 required keyword-only argument: 'last'
>>>

We will look at keyword arguments in the next section.

5. Functions with indefinite number of keyword arguments (**)

Apart simple arguments you may want to use an indefinite number of keyword arguments (key=value) in your function definition. In this case you mark the variable containing your key=value pairs with ** operator.

>>> def add_user(name, **info):
            ... user = {"name": name}
            ... user.update(dict(info))
            ... return user
            ...
>>> add_user("Elena")
{'name': 'Elena'}
>>> add_user("Elena", email="elena@users.com")
{'name': 'Elena', 'email': 'elena@users.com'}
>>> add_user("Elena", email="elena@users.com", last_connection="26 Oct 2016 12:16:23 GMT")
{'name': 'Elena', 'last_connection': '26 Oct 2016 12:16:23 GMT', 'email': 'elena@users.com'}
>>>

A more pythonic way to implement this function would be:

>>> def add_user(name, **info):
              ... return dict({"name": name}, **info)
              ...
>>> add_user("Elena")
{'name': 'Elena'}
>>> add_user("Elena", email="elena@users.com")
{'name': 'Elena', 'email': 'elena@users.com'}
>>> add_user("Elena", email="elena@users.com", last_connection="26 Oct 2016 12:16:23 GMT")
{'name': 'Elena', 'last_connection': '26 Oct 2016 12:16:23 GMT', 'email': 'elena@users.com'}
>>>

Here the dict() function unpacks the info variable into the key-value arguments and adds them to the already present key-value pair ({“name”: name}).

And off course, you can combine both simple and keyword arguments in a function definition:

>>> def add_user(name, *ips, **info):
              ...

Note: A ** argument can only be the last argument.

In our next post, we will take a closer look at list comprehension idioms in python.

If you have any comments, questions or thoughts, please leave them below. Till next time!

The following two tabs change content below.