Asterisks in Python

Arpit Omprakash
Byte-Sized-Code
Published in
8 min readJun 20, 2020

What They Are and How to Use Them

Most of us have encountered code like this (and some of us will be seeing it for their first time):

>>> numbers = [2, 1, 3, 4, 7] 
>>> more_numbers = [*numbers, 11, 18]
>>> print(*more_numbers, sep=', ')
2, 1, 3, 4, 7, 11, 18

The first time I looked at it, I was quite puzzled. Isn’t asterisk (*) supposed to be the operator for multiplication? After some reading, I found out that * and **, when used before a variable, are known as prefix operators. They are also the representations of the infix operators for multiplication (*) and exponentiation (**), but we won’t be looking into that today.

So What Are We Looking into Today?

We look exclusively into the prefix behavior of the operators, i.e., the * and ** operators that are used before a variable, as seen above. Two examples of usage of * are shown in the code above.
Asterisks can be used for the following purposes:

  • Using * and ** to pass arguments to a function
  • Using * and ** to capture arguments passed into a function
  • Using * to accept keyword-only arguments
  • Using * to capture items during tuple unpacking
  • Using * to unpack iterables into a list/tuple
  • Using ** to unpack dictionaries into other dictionaries

Even for the veterans (especially people who have recently migrated from Python 2 to 3), there are things to learn as Python has added new capabilities for these functions. Thus, I request everyone to go through the code blocks at least once to make sure you know the stuff.

Unpacking into a Function Call

When using a function call, we can use an asterisk (*) to unpack an iterable into the function call. For example:

>>> numbers = [1,2,3,12,4414,1,2]
>>> print(*numbers)
1 2 3 12 4414 1 2

The asterisk in the print function passes all the elements of the numbers list to the print function without us ever needing to know how many elements are present in the list. Quite useful when taking in user input as an iterable.

Here’s another useful (and a bit complicated) example:

>>> def transpose(matrix):
... return [list(row) for row in zip(*matrix)]
...
>>> transpose([[1,2,3],[4,5,6],[7,8,9]])
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]

The transpose function is first zipping all the rows (effectively turning them into column tuples) in the original matrix and then printing out their contents as a list of lists (matrix).

The ** operator does something similar, but with key-value pairs. It can be used to unpack a dictionary into a function call. Here’s an example:

>>> date_info = {'year': "2019", 'month': "01", 'day': "31"} 
>>> filename = "{year}-{month}-{day}.txt".format(**date_info)
>>> filename
'2019-01-31.txt'

As of Python 3.8, both * and ** can be used multiple times in a single function call:

>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato'] 
>>> numbers = [2, 1, 3, 4, 7]
>>> print(*numbers, *fruits)
2 1 3 4 7 lemon pear watermelon tomato
>>> date_info = {'year': "2020", 'month': "01", 'day': "01"}
>>> track_info = {'artist': "Beethoven", 'title': 'Symphony No 5'}
>>> filename = "{year}-{month}-{day}-{artist}-{title}.txt".format(
... **date_info,
... **track_info,)
...
>>> filename
'2020-01-01-Beethoven-Symphony No 5.txt'

You need to be careful while using ** multiple times though, the different dictionaries should never have a matching key in them, as keywords for function calls are unique and repeats are not allowed.

Packing Arguments given to a Function

The * operator can be used to capture an unlimited number of positional arguments given to a function. These arguments are captured into a tuple.

>>> def make_list(*items):
... return [item for item in items]

The function will take any number of positional arguments and convert them all into a single list:

>>> make_list(1,23,4,12,4,1,5,1)
[1, 23, 4, 12, 4, 1, 5, 1]

Similarly, we can use the ** operator to capture any keyword arguments that are passed onto a given function.

>>> def tag(tag_name, **attributes):
... attribute_list = [f'{name}="{value}"' for name, value in attributes.items()]
... return f"<{tag_name} {' '.join(attribute_list)}>"
...

The function above will capture the tag name and any number of attributes that you throw at it and then convert it into a well-formatted HTML tag. Here’s an example usage:

>>> tag('a', href="https://aceking007.github.io")
'<a href="https://aceking007.github.io">'

Types of Function Arguments

Arguments with names are called keyword arguments, and the other arguments are called positional arguments. Let’s first understand the difference between keyword and positional arguments. This part is a short detour before going into the primary use of asterisks. Feel free to skip this if you are already familiar with this.

The other kinds of Arguments

Positional Arguments

Positional arguments are arguments passed onto a function that needs to be included in the proper position or order. I.e., the first positional argument must be provided first, followed by the second positional argument, and so on.

An example can be seen in Python’s complex function. The order in which you provide the numbers determines if they are real or imaginary parts. The real part needs to be provided as the first argument, followed by the imaginary part. For example,

>>> complex(1,2)
(1+2j)
>>> complex(2,1)
(2+1j)

Here, the relative positions of the arguments determine what the output will be, and thus, they are called positional arguments.

Keyword Arguments

A keyword argument is provided as a key-value pair separated by an equals (=) sign. The general form of such arguments is function(keyword=value) .

Python’s complex function can also accept two keyword arguments, called real and imag. Keyword arguments are provided to functions after any required positional arguments. But the order of keyword arguments is not taken does not matter. Note this in the example:

>>> complex(real=3, imag=5)
(3+5j)
>>> complex(imag=5, real=3)
(3+5j)

Both the orders produce the same number. Now that we are clear about keyword and positional arguments let’s move on to the main topic.

Keyword-Only Arguments

Keyword-Only arguments are arguments that can only be specified by using keywords (they can’t be positionally specified). As we saw above, we can define the arguments real and imag of the complex function both by using keywords and also positionally.

However, in 2006, via PEP 3102, Python introduced functions that can be defined to take Keyword-Only arguments. There is a special syntax for defining such functions as discussed below.

Keyword-Only Arguments With Positional Arguments

Arguments defined after the * in a function will behave as keyword-only arguments. We can often have a combination of positional and keyword-only arguments in functions that we create. For example:

>>> def pos_kw(name, *items, val, truth=False):
... print(name)
... print(items)
... print(val)
... print(truth)
...
>>> pos_kw('good', 1,2,3,4,1,2, val=78, truth=True)
good
(1, 2, 3, 4, 1, 2)
78
True

The last two arguments defined (val and truth) are keyword-only as they are defined after the asterisk (*) argument. They can not be assigned positionally. Trying to input the value 78 positionally for val will produce the following error:

>>> pos_kw('good', 1,2,3,4,1,2, 78)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pos_kw() missing 1 required keyword-only argument: 'val'

Thus, we can use the above template to create functions that take in positional as well as keyword-only arguments.

Keyword-Only Arguments Without Positional Arguments

Similarly, we can use just the asterisk in the beginning to create functions that do not take any positional or keyword arguments but take only keyword-only arguments.

>>> def sample(*, name):
... print(name)
...
>>> sample(name='Arpit')
Arpit

If we try to just pass on any argument without the keyword, it will result in an error.

>>> sample('Arpit')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sample() takes 0 positional arguments but 1 was given

An excellent example of this is Python’s built-in sorted function. If you use help on sorted you can see this:

>>> help(sorted)
Help on built-in function sorted in module builtins:
sorted(iterable, /, *, key=None, reverse=False)
Return a new list containing all items from the iterable in ascending order.
A custom key function can be supplied to customize the sort order, and the
reverse flag can be set to request the result in descending order.

The arguments key and reverse are keyword-only arguments used in the function that can’t be passed on without a keyword.

Unpacking Tuples using Asterisks

Python 3 added this functionality, and it allows us to unpack the contents of a tuple into variables using the * operator.

>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato'] 
>>> first, second, *remaining = fruits
>>> remaining
['watermelon', 'tomato']
>>> first, *remaining = fruits
>>> remaining
['pear', 'watermelon', 'tomato']
>>> first, *middle, last = fruits
>>> middle
['pear', 'watermelon']

It is also possible to use multiple asterisks by nested tuple unpacking, as shown below:

>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']
>>> ((first_letter, *remaining), *other_fruits) = fruits
>>> remaining
['e', 'm', 'o', 'n']
>>> other_fruits
['pear', 'watermelon', 'tomato']

Check out this PEP for more information. It’s a short one and gives you an idea of where you can use it to better your code.

Asterisks in List Literals

Python 3.5 marks the addition of many features to the asterisk operators through the PEP448. Probably one of the most prominent new features is the ability to use * to dump an iterable into a new list.

Say you have the given function:

>>> def palindromify(text):
... return list(text) + list(text[::-1])
...
>>> palindromify('name')
['n', 'a', 'm', 'e', 'e', 'm', 'a', 'n']

It can be quite easily modified to:

>>> def palindromify(text):
... return [*text , *text[::-1]]
...
>>> palindromify('name')
['n', 'a', 'm', 'e', 'e', 'm', 'a', 'n']

which is more readable and efficient (compared to the two calls to list). The important thing is this method is not limited to creating lists. We can similarly use the * operator to create tuples and sets. Here is an example:

>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato'] 
>>> (*fruits[1:], fruits[0])
('pear', 'watermelon', 'tomato', 'lemon')
>>> uppercase_fruits = (f.upper() for f in fruits)
>>> {*fruits, *uppercase_fruits}
{'lemon', 'watermelon', 'TOMATO', 'LEMON', 'PEAR', 'WATERMELON', 'tomato', 'pear'}

Note: It wasn’t that easy to accomplish the last thing we did in a single line. However, one had to depend on this obscure method that I found out some time ago:

>>> set().union(fruits, uppercase_fruits)
{'lemon', 'watermelon', 'TOMATO', 'LEMON', 'PEAR', 'WATERMELON', 'tomato', 'pear'}

Asterisks in Dictionary Literals

Similar to what * does for lists or tuples, ** does the same for dictionaries. The same PEP448 introduced a better and shorter way of dumping key-value pairs from dictionaries in Python using **.

>>> date_info = {'year': "2020", 'month': "01", 'day': "01"} 
>>> track_info = {'artist': "Beethoven", 'title': 'Symphony No 5'}
>>> all_info = {**date_info, **track_info}
>>> all_info
{'year': '2020', 'month': '01', 'day': '01', 'artist': 'Beethoven', 'title': 'Symphony No 5'}

Using ** also allows us to add/update values while we copy a dictionary. For example:

>>> date_info = {'year': '2020', 'month': '01', 'day': '7'} 
>>> event_info = {**date_info, 'group': "Python Meetup", 'day': 14}
>>> event_info
{'year': '2020', 'month': '01', 'day': '14', 'group': 'Python Meetup'}

Conclusion

Python’s * and ** aren’t just syntactic sugar used by fancy people. Some things that they do can be achieved by other means, but the alternatives generally tend to be cumbersome and resource inefficient. Whereas, there are some things (Keyword-Only arguments, passing unlimited arguments to functions) that can not be achieved by other means.

If you are new to Python or are concerned about how to remember all these uses, don’t be! These operators have different applications and you aren’t required to memorize all of them. Getting a feel for when you might be using one of them is generally enough. You can always use this article as a cheat sheet and refer back as many times as you like.

Before You Go…

If you liked the article, give a clap (it’s free, and you can clap up to 50 times!), share, recommend, and respond to it. Your claps, shares, and responses encourage me to write more such articles.

--

--

Arpit Omprakash
Byte-Sized-Code

I'm a Programming and Statistics enthusiast studying Biology. To find out if we have common interests, have a look at my thoughts: https://aceking007.github.io/