Home Python *args, **kwargs, and Star/Asterisk-Operator Explained in Simple Terms (With Code Snippets)
Post
Cancel

Python *args, **kwargs, and Star/Asterisk-Operator Explained in Simple Terms (With Code Snippets)

You just discovered a cool Python module that helps you to implement your program much more elegantly or even faster and while you are digging through the code and documentation you come across *args and **kwargs in functions and classes of the module and ask yourself what do those two cryptic things do? In this article, you will learn the meaning behind the python * and ** operators, not to be confused with multiplication and power, and how those are used together with parameters args and kwargs.

Python Sequential Unpack *-Operator and *args

Python allows multiple variable assignments in one line. For example, the variables x, y, and z can be assigned using the following line where x is assigned 1, y is assigned 2, and z is assigned 3.

1
x, y, z = 1, 2, 3

In the code above, the values 1, 2, and 3 are single values. But it is also possible to assign the variables x, y, and z using sequential data structures such as lists, tuples, sets, and even strings. Consider the following code, where sequential data structures are used to assign values to variables:

1
2
3
4
5
6
7
8
9
t = (1, 2, 3)
l = ['Hello', 'World', '!']
s = {True, False, None}
string = "abc"

x, y, z = t
w, v, u = l
h, j, k = s
a, b, c = string

All four data structures, t (tuple), l (list), s (set), and string (str) contain three values which can be used to assign three variables at the same time while the order of the values is the order of assignment from left to right. This requires that on the left-hand side of the = are as many variables as values are on the right-hand side. Otherwise, an error will occur. For example, if you want to assign only two variables using a four element long data structure, you can use the variable named _ instead of an actual variable:

1
x, _, y, _ = (1, 2, 3, 4)

_ is the variable name for unused values by convention. (For completeness, in the example above, _ is first assigned to 2 and then to 4)

In the code snippets above, the sequential unpacking was used implicitly by assigning multiple variables at once. But sequential unpacking can also be triggered explicitly using the *-operator. Take a look at the following code:

1
2
3
4
5
6
7
def multiply_accumulate(a, b, c):
    return a+b*c

t = (1, 2, 3)

multiply_accumulate(t[0], t[1], t[2])
# > 9

The function multiply_accumulate expects three arguments. But instead of passing each value of a sequential data structure individually using an index, you can unpack them using the *-operator:

1
2
multiply_accumulate(*t)
# > 9

The values of t are assigned to the parameters a, b, and c using the sequential unpack *-operator.

The *-operator can also be used in the function signature. Using a *-operator in front of a parameter name signifies that a sequence of unnamed arguments can be passed to the function. Consider the following code:

1
2
3
4
5
6
7
8
def add(*args):
    sum = 0
    for arg in args:
        sum += arg
    return sum

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

The function add has the parameter *args which allows you to pass a variable number of unnamed arguments when calling add. In the example above, the arguments 1, 2, 3, and 4 are bundled together into a tuple which can then be used to iterate over.

A function signature can also contain named parameters and a sequence of unnamed parameters. If the named parameters come before the sequential parameter, the function can be called without passing the parameter name of the named parameter. Otherwise, the name of the named parameter must be passed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def f0(c, *args)
    print(c)
    print(args)

f0(1, 2)
# > 1
# > (2,)

def f1(*args, c):
    print(args)
    print(c)

f1(1, c)
# > f1() missing 1 required keyword-only argument: 'c'

# > (1,)
# > 2
f1(1, c=2)

Only one parameter in a function signature can be a sequence of unnamed parameters marked with the *-operator. You can try to declare a function having two parameters with the *-operator in front; however, the Python parser won’t run your program:

1
2
3
4
5
def f2(*a, *b):
  print(a)
  print(b)

# > SyntaxError: * argument may appear only once

Python double Star/Asterisk-Operator ** and **kwargs

Now that you know the unpacking operator * for sequential data types and parameters, let’s take a look at the **-operator (double asterisk operator) for unpacking dictionaries. While *-operator in a function signature allows you to pass a variable number of unnamed arguments, the **-operator lets you pass a variable amount of named or keyword arguments:

1
2
3
4
5
6
7
8
def print_parameters(**kwargs):
    for k, v in kwargs.items():
        print(f"{k}->{v}")

print_parameters(a=1, b=2, c=3)
# > a->1
# > b->2
# > c->3

Inside the function above, kwargs is cast into a dictionary, where the keys are the argument names and the values are the argument values set in the function call.

As with the *-operator the **-operator can be used with named parameters and even with the *-opeator one function signature:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def f3(a, b, c, *args, **kwargs)
    print(f"a->{a}")
    print(f"b->{b}")
    print(f"c->{c}")
    for arg in args:
        print(arg)
    for k, v in kwargs.items():
        print(f"{k}->{v}")

f3(1,2,3,4,6,f=5,g=7)
# > a->1
# > b->2
# > c->3
# > 4
# > 6
# > f->5
# > g->7

But the parameter with the *-operator in front must always come before the parameter with the **-operator:

1
2
3
def f4(**kwargs, *args):
    ...
# > SyntaxError: arguments cannot follow var-keyword argument

Even though it is not required to name the parameters args and kwargs it is pretty common in most Python modules to call them like that and can be seen as a de-facto standard.

The **-operator can also be used when calling a function and passing a dictionary containing the arguments which should be passed:

1
2
3
4
5
6
7
8
9
10
11
def f5(a, b, c):
    print(a)
    print(b)
    print(c)

d = {'a':1, 'b':2, 'c':3}

f5(**d)
# > 1
# > 2
# > 3

This is not only possible with a fixed amount of named parameters, this also works with variable amount of named parameters:

1
2
3
4
5
6
7
8
9
10
def f6(**kwargs):
    for k, v in kwargs.items():
        print(f"{k}->{v}")

d = {'a':1, 'b':2, 'c':3}

f6(**d)
# > a->1
# > b->2
# > c->3

And even a mix of both:

1
2
3
4
5
6
7
8
9
10
11
12
def f7(a, b, **kwargs):
    print(a)
    print(b)
    for k, v in kwargs.items():
        print(f"{k}->{v}")

d = {'a':1, 'b':2, 'c':3}

f7(**d)
# > 1
# > 2
# > c->3

With this article on Python *args and **kwargs, you have learned all how to use the sequential unpacking operator * and how to pass an arbitrary amount of named and unnamed arguments to a function. Make sure to get the free Python Cheat Sheets in my Gumroad shop. If you have any questions about this article, feel free to join our Discord community to ask them over there.

This post is licensed under CC BY 4.0 by the author.