⇦ Back

This page is about *args and **kwargs.

In other words, it’s about arguments and keyword arguments, and about what that single- and double-asterisk notation means.

1 Asterisk Operations vs Asterisk Notations

Firstly, let’s mention what asterisk notation is not. It’s not a type of operation. This is confusing, because a single asterisk can be used in Python for multiplication and double asterisks can be used to raise to a power, and both of these are operations:

# A single asterisk performs multiplication
x = 5 * 3
print(x)
## 15
# Double asterisks perform exponentiation
x = 5**3
print(x)
## 125

This is not what this page is about. Instead, it is about the single- and double-asterisk notations which differ from the above operations in that they do not augment and return objects. If we try to treat them as operations, we get errors:

# This will produce an error
x = *3
SyntaxError: can't use starred expression here
# This will produce an error
x = **3
SyntaxError: invalid syntax

2 What is Asterisk Notation?

Asterisk notation only has meaning when used with the inputs to functions. More specifically:

  • The single-asterisk notation means ‘the unnamed inputs to a function’
  • The double-asterisk notation means ‘the named inputs to a function’

As an example, let’s take the print() function which can have multiple inputs to it:

print('A', 'B', 'C')
## A B C

In that example, it had three inputs. Here’s another example where those same three inputs have been bundled together into a list:

ls = ['A', 'B', 'C']
print(ls)
## ['A', 'B', 'C']

But hang on, those outputs are not the same! “A B C”" is not the same as “['A', 'B', 'C']”.

The difference is that the first example had three inputs (‘A’, ‘B’ and ‘C’) whereas the second example had only one input (ls). The fact that this one input had three elements doesn’t change the fact that it’s one list! The single-asterisk notation, however, does change this: it causes the list ls to be interpreted as ‘the inputs to the function’, and so print() interprets it as three things:

print(*ls)
## A B C

This is the same output as the first example. The single asterisk has unpacked the list into three separate items.

3 Single-Asterisk Notation with a User-Defined Function

Instead of a built-in function like print(), here’s the single-asterisk notation in action with a custom function. It demonstrates the real usefulness of this notation: the fact that the number of inputs can be as many or as few as you want. Usually, you would need to specify in some way how many arguments are expected.

def example_function(*args):
    print(args)

With this function, any number of input can be given:

example_function('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J')
## ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J')

This output is equivalent to:

ls = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
example_function(*ls)
## ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J')

As you can see, the output is a tuple. This may be unexpected for two reasons:

  • It’s not a list, despite ls having originally been a list
  • It’s one thing (a tuple), not ten things

The easiest way to think about this is to consider the two asterisks (the one in the function call - *ls - and the one in the function definition - *args) to have cancelled each other out: the first asterisk converted the single list into ten arguments and the second asterisk converted those ten arguments back into a single thing (and a tuple is just the default output format of this conversion).

Note that, as mentioned previously, this conversion only has meaning in the context of function arguments. You can’t use single-asterisk notation to split lists into multiple objects or to concatenate multiple objects into a tuple in general. If you try, you’ll probably get a syntax error:

ls = ['A', 'B', 'C']
tup = *ls
SyntaxError: can't use starred expression here
ls = *(*ls)
SyntaxError: cannot use starred expression here

3.1 Examples to Compare

Here’s a function that takes one input - a list - and adds its elements together:

def add_numbers(args):
    """Add a bunch of numbers together."""
    running_total = 0
    for number in args:
        running_total += number
    return running_total


print(add_numbers([1, 2, 3, 4]))
## 10

Now, here’s the same function except it takes multiple inputs and converts them all into a tuple:

def add_numbers(*args):
    """Add a bunch of numbers together."""
    running_total = 0
    for number in args:
        running_total += number
    return running_total


print(add_numbers(1, 2, 3, 4))
## 10

The difference is that the second function had the single-asterisk notation in the definition of its input: *args vs args.

For completeness, here’s the second function again but being given a list that is then interpreted as multiple inputs (which is equivalent to the second example):

ls = [1, 2, 3, 4]
print(add_numbers(*ls))
## 10

3.2 Another Example

Single-asterisk notation is useful when augmenting only part of a list of things. For example, imagine we want to import a file from one location then export it to a different-but-similar location:

from pathlib import Path

# Create a path to a file
path = Path(Path.home(), 'Input Folder', 'Random Sub-Folder', 'File.txt')
print(path)
## /home/rowan/Input Folder/Random Sub-Folder/File.txt

When we want to export it we can augment the path like this:

path = Path(Path.home(), 'Output Folder', *path.parts[-2:])
print(path)
## /home/rowan/Output Folder/Random Sub-Folder/File.txt

Just a note that if you do something like this it’s good practice to double check that the output folder exists before you try to export to it:

import os

os.makedirs(path.parents[0], exist_ok=True)

4 Double-Asterisk Notation

Whereas single-asterisk notation is used with unnamed inputs to functions, double-asterisk notation is used with named inputs to functions. Instead of lists and tuples, the relevant data structure is the dictionary:

def example_function(**kwargs):
    print(kwargs)


example_function(first=1, second=2, third=3)
## {'first': 1, 'second': 2, 'third': 3}

The above is equivalent to:

dct = {
    'first': 1,
    'second': 2,
    'third': 3
}
example_function(**dct)
## {'first': 1, 'second': 2, 'third': 3}

The double-asterisk splits the dictionary up into three key-value pairs which then get used as the named inputs to the function. They are then converted back into a dictionary before being printed.

4.1 Another Example

Here’s a function that calculates a rugby team’s score from the methods in which they scored:

def rugby_score(**kwargs):
    score = 0
    score = score + kwargs['tries'] * 5
    score = score + kwargs['conversions'] * 2
    score = score + kwargs['penalties'] * 3
    score = score + kwargs['drop_kicks'] * 3
    return score


score = rugby_score(tries=3, conversions=2, penalties=3, drop_kicks=1)
print(score)
## 31

The above is equivalent to:

scores_in_a_rugby_match = {
    'tries': 3,
    'conversions': 2,
    'penalties': 3,
    'drop_kicks': 1
}
score = rugby_score(**scores_in_a_rugby_match)
print(score)
## 31

Contrast this to the following example where ONLY a dictionary can be used as an input (as opposed to keyword arguments):

def rugby_score(kwargs):
    score = 0
    score = score + kwargs['tries'] * 5
    score = score + kwargs['conversions'] * 2
    score = score + kwargs['penalties'] * 3
    score = score + kwargs['drop_kicks'] * 3
    return score


score = rugby_score(scores_in_a_rugby_match)
print(score)
## 31

So, in summary, the value of single-asterisk notation is that it allows functions to take either a list or multiple arguments as inputs (with there being no need to specify the length of the list or the number of arguments). Similarly, the value of double-asterisk notation is that it allows function to take either a dictionary or multiple keyword arguments as inputs (again with there being no need to specify the length or number of inputs).

⇦ Back