All Articles

All you need to know about Asterisks in Python

An Architectural Asterisk

Most developers know the asterisk (*) character as multiplication operator in Python:

product = 4 * 2  # 8

However, the asterisk has some special meaning for list or dictionary data structures.

*args and **kwargs

*args in the function definition

The most widely known usage of the asterisk operator, also known as destructuring operator, in Python is its usage in functions.

Let’s say you have a function that can add values and returns the sum of these values:

def add(number_1, number_2):
    return number_1 + number_2

print(add(1,2)) # 3

What if you want to sum up an arbitrary number of values? You can add an asterisk in front of the argument’s name like so:

def add(*numbers):
    sum = 0
    for number in numbers:
        sum += number
    return sum

As you might have already noticed from the function’s body, we expect numbers to be an iterable. Indeed, if you check the type, numbers is a tuple. And, indeed, we can now call the function with an arbitrary number of arguments:

add(1, 2, 3, 4) # 10

* in the function call

We have seen so far that we can define a function so that it takes a list of parameters. But what if we have a function with a fixed set of arguments and we want to pass a list of values to it. Consider this function:

def add(number_1, number_2, number_3):
    return number_1 + number_2 + number_3

This function takes exactly 3 parameters. Let’s assume we have a list with exactly three elements. Of course, we could call our function like this:

my_list = [1, 2, 3]

add(my_list[0], my_list[1], my_list[2])

Luckily the destructuring operator (*) works in both ways. We have already seen the usage in a function’s definition, but we can use it also to call a function:

my_list = [1, 2, 3]

add(*my_list)

**kwargs in function definition

In the same way we can destructure lists with the asterisk (*) operator, we can use the double-asterisk (**) operator to destructure dictionaries in Python functions.

Consider a function like this:

def change_user_details(username, email, phone, date_of_birth, street_address):
    user = get_user(username)
    user.email = email
    user.phone = phone
    ...

If we call it with keyword arguments (kwargs), a function call might look like this:

change_user_details('bascodes', email='blog@bascodes.example.com', phone='...', ...)

In that case we could rewrite our function like this to accept an arbitrary number of keyword arguments that are then represented as a dictionary called kwargs:

def change_user_details(username, **kwargs):
    user = get_user(username)
    user.email = kwargs['email']
    user.phone = kwargs['phone']
    ...

Of course, we can use the kwargs dictionary like any other dictionary in Python, so that the function might become a bit cleaner if we actually utilize the dictionary data structure like so:

def change_user_details(username, **kwargs):
    user = get_user(username)
    for attribute, value in kwargs.items():
        setattr(user, attribute, value)
    ...

** in the function call

Of course, the ** operator works for calling a function as well:

details = {
    'email': 'blog@bascodes.example.com',
    ...
}
change_user_detail('bascodes', **details)

Restricting how functions are called

Keyword arguments only

One of the most surprising features of the asterisk in function definitions is that it can be used standalone, i.e. without a variable (parameter) name. That being said, this is a perfectly valid function definition in Python:

def my_function(*, keyword_arg_1):
    ...

But what does the standalone asterisk do in that case? The asterisk catches all (non-keyword) arguments in a list as we have seen above. There is no variable name in our case that would make the list. After the * we have a variable called keyword_arg_1. Since the * has already matched all positional arguments, we’re left with keyword_arg_1, which must be used as a keyword argument.

Calling the above function with my_function(1) will raise an error:

TypeError: my_function() takes 0 positional arguments but 1 was given

Positional arguments only

What if we want to force the users of our function to use positional arguments only – as opposed to keyword arguments only in the last example?

Well, there is a very Pythonic way. We interpret the / sign (the opposite of multiplication) to do the trick:

def only_positional_arguments(arg1, arg2, /):
    ...

Surprisingly few Python developers know about this trick which has been introduced to Python 3.8 through PEP 570.

If you call the last function with only_positional_arguments(arg1=1, arg2=2), this will raise a TypeError:

TypeError: only_positional_arguments() got some positional-only arguments passed as keyword arguments: 'arg1, arg2'

Usage of * and ** in literals

The asterisk (*) and double asterisk (**) operators do not only work for function definitions and calls, but they can be used to construct lists and dictionaries.

Constructing lists

Let’s say, we have two lists and want to merge them.

my_list_1 = [1, 2, 3]
my_list_2 = [10, 20, 30]

Of course, we could merge these lists with the + operator:

merged_list = my_list_1 + my_list_2

However, the * operator gives us a bit more flexibility. Let’s say, we want to include a scalar value in the middle, we could use:

some_value = 42
merged_list = [*my_list_1, some_value, *my_list_2]

# [1, 2, 3, 42, 10, 20, 30]

Constructing dictionaries

Again, what is true for lists and the asterisk (*) operator is true for dictionaries and the double-asterisk operator (**).

social_media_details = {
    'twitter': 'bascodes'
}

contact_details = {
    'email': 'blog@bascodes.example.com'
}

user_dict = {'username': 'bas', **social_media_details, **contact_details}
# {'email': 'blog@bascodes.example.com', 'twitter': 'bascodes', 'username': 'bas'}

Destructuring lists

You might already know, that you can split elements of a list to multiple variables like so:

my_list = [1, 2, 3]
a, b, c = my_list

# a -> 1
# b -> 2
# c -> 3

But did you know that you can use the asterisks (*) to assign to variables when you have a list of arbitrary length?

Say, you want the first element and the last one of a list in a specific variable.

You can do this by using the asterisk like so:

my_list = [1, 2, 3]
a, *b, c = my_list

In this example a now contains the first element, and c contains the last element of my_list. What is in b, however?

In b, there is the entire list, excluding the first and last element, i.e. [2]. Note that b is a list now.

This idea might get a bit clearer, when we look at larger lists:

my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a, *b, c = my_list

# a -> 1
# b -> [2, 3, 4, 5, 6, 7, 8, 9]
# c -> 10