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.
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
Asterisk notation only has meaning when used with the inputs to functions. More specifically:
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.
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:
ls
having originally been a listThe 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
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
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)
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.
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).