*args **kwargs Python

Introduction

In Python you can create functions that accept an unknown-in-advance number of arguments.

This is very convenient, and besides there are two options:

Instead of the words args, kwargs you can use others, the main thing is that the number of * is correct

Nevertheless, if there are no special reasons against it, it is desirable to follow common practice.

Before studying this article I advise you to get acquainted with the chapter Parameters and Arguments from the article Functions

In English-language literature the terms Extended Formal Argument Syntax and Arbitrary Keyword Arguments are used

With *args an unknown-in-advance number of positional arguments .

With **kwargs an unknown-in-advance number of keyword arguments .

Let's start with *args

*args

Let's consider the script args_demo.py

def myfunc(*args): print(args) print(type(args)) myfunc(50, 70, 120, 3, 14) myfunc('Barcelona', 'Malaga', 'Riga')

python args_demo.py

python args.py (50, 70, 120, 3, 14) <class 'tuple'> ('Barcelona', 'Malaga', 'Riga') <class 'tuple'>

As you can see from the example: the same function was able to process first five arguments of type int and then three arguments of type str

You only need to use * in the function declaration.

The arguments are passed as a tuple

Nothing prevents you from iterating over the received arguments one by one.

Let's use a loop for

def myfunc(*args): for item in args: print(item) myfunc(50, 70, 120, 3, 14) myfunc('a','a','a')

python args_demo.py

50
70
120
3
14
a
a
a

An example of a function that adds an unknown-in-advance number of arguments

def my_sum(*args): i = iter(args) mysum = next(i) for ar in i: mysum += ar return mysum print(my_sum(50, 70, 120, 3, 14)) print(my_sum('a', 'b', 'c'))

257 abc

An example of a function that multiplies an unknown-in-advance number of arguments.

For the demonstration we use *length instead of *args

def hypervolume(*lengths): i = iter(lengths) v = next(i) for length in i: v *= length return v print(hypervolume(2, 4)) # 8 print(hypervolume(2, 4, 6)) # 48 print(hypervolume(2, 4, 6, 8)) # 384 print(hypervolume(1)) # 384

python hypervolume.py

8 48 384 1

print(hypervolume())

Traceback (most recent call last): File "/home/andrei/python/hypervolume.py", line 14, in <module> print(hypervolume()) File "/home/andrei/python/hypervolume.py", line 3, in hypervolume v = next(i) StopIteration

You can replace this error with a clearer one by changing the function code so that one positional argument is taken first.

def hypervolume(length, *lengths): v = length for item in lengths: v *= item return v print(hypervolume(2, 4)) # 8 print(hypervolume(2, 4, 6)) # 48 print(hypervolume(2, 4, 6, 8)) # 384 print(hypervolume(1)) # 1 print(hypervolume())

8 48 384 1 Traceback (most recent call last): File "/home/andrei/python/hypervolume.py", line 30, in <module> print(hypervolume()) TypeError: hypervolume() missing 1 required positional argument: 'length'

The technique shown above of using a positional argument for a mandatory argument and * arguments for optional ones is quite popular.

**kwargs

Using **kwargs allows you to pass into a function not simple arguments, but arguments in the form of keywords.

This is very convenient if you need to work with different custom scenarios - you do not need to introduce some specific order of arguments, as in Bash scripts

Each argument gets its own name and can be processed regardless of order.

Methods

There are built-in methods for working with kwargs

Let's start with the keys() and values() methods which return the key name and the value.

Let's call the function filterkw() with three key arguments a=1, b=2, c=3 and print them to the terminal separately from each other

def filterkw(**kwargs): for k in kwargs.keys(): print(f"key: {k}") for v in kwargs.values(): print(f"value: {v}") if __name__ == "__main__": filterkw(a=1, b=2, c=3)

key: a key: b key: c value: 1 value: 2 value: 3

The get() method returns the value by key

def filterkw(**kwargs): filters = ["b", "c"] for f in filters: print(kwargs.get(f)) if __name__ == "__main__": filterkw(a=1, b=2, c=3)

2 3

If there is no such key get() returns None

def filterkw(**kwargs): filters = ["b", "c", "z"] for f in filters: print(kwargs.get(f)) if __name__ == "__main__": filterkw(a=1, b=2, c=3)

2 3 None

Example

Let's consider the script kwargs_demo.py

def myfunc(**kwargs): if 'website' in kwargs: print('Visit the site {}'.format(kwargs['website'])) else: print('Visit topbicycle.ru') myfunc(website='HeiHei.ru') myfunc(localsite='aredel.com') myfunc(website='urn.su', author='Andrey Olegovich')

python kwargs_demo.py

Visit the site HeiHei.ru
Visit topbicycle.ru
Visit the site urn.su

The function expects an argument with the key website

On the first call such an argument comes alone

On the second call the website key does not come and else is triggered

During the third call website and author come, but the function does not wait for author and it is simply ignored

Let's add one more call of the function

myfunc(website='urn.su', author='Andrey Olegovich', 2)

python kwargs_demo.py

File "args_demo.py", line 29 myfunc3(website='urn.su', author='Andrey Olegovich', 2) SyntaxError: non-keyword arg after keyword arg

As you can see, the function does not want to accept a regular argument. To find out how this problem is solved - go to the next chapter.

*args **kwargs

To call functions with both positional and keyword arguments you need to specify both *args and **kwargs in the function declaration.

When calling the function first you need to list all positional (regular) arguments and then the keyword (key) ones

Let's consider the script args_kwargs_demo.py

def myfunc(*args,**kwargs): if 'website' in kwargs: print('Visit the site {}'.format(kwargs['website'])) else: print('Visit topbicycle.ru') myfunc(2, website='urn.su', author='Andrey Olegovich')

python args_kwargs_demo.py

Visit the site urn.su

If you got an error

File "args_demo.py", line 21 SyntaxError: Non-ASCII character '\xd0' in file args_demo.py on line 21, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

Add the following code on the first line

# coding=utf-8

Details here

One of the most common uses of *args, **kwargs is argument forwarding (Argument Forwarding)

Filtering **kwargs

Sometimes it is necessary to perform actions only with certain keyword arguments.

The rest need to be filtered out. You will learn how to do this with the help of a list generator (list comprehension) in this paragraph.

Suppose the function filterkw() accepts **kwargs.

Let's create a list of the required ones and print only them to the screen.

def filterkw(**kwargs): filters = ["limit", "offset", "sortkey", "sortdir"] params = {k: kwargs[k] for k in filters if k in kwargs.keys()} print(params) if __name__ == '__main__': filterkw(test=2, more=3, new=40) filterkw(limit=25, some=50, offset=40) filterkw(limit=1, offset=2, sortkey=90, sortdir=100)


{'limit': 25, 'offset': 40}
{'limit': 1, 'offset': 2, 'sortkey': 90, 'sortdir': 100}

The first call led to nothing, since neither test, nor more, nor new are the required keys.

The second call contained two required keys limit and offset. some was ignored

The third call contained all the required keys and they were all printed to the screen

kwargs.get()

An example of filtering arguments using get()

def filterkw(**kwargs): filters = ["a", "e", "i", "o"] params = {k: kwargs.get(k) for k in filters if kwargs.get(k)} print(params) if __name__ == "__main__": filterkw(a=1, b=2, c=3, d=4, e=5)

python list_comp_filter.py

{'a': 1, 'e': 5}

With kwargs.get() you can set default values for kw arguments depending on other arguments.

def set_kw_arg(url, auth, **kwargs): pwd_prompt = kwargs.get( "expect_pwd_prompt", False if auth.lower() == "cert" else True ) print(pwd_prompt) if __name__ == "__main__": set_kw_arg("https://heihei.ru", "cert") # Will use default -> False set_kw_arg("https://heihei.ru", "cert", expect_pwd_prompt=True) # True set_kw_arg("https://heihei.ru", "pwd") # Will use default -> True set_kw_arg("https://heihei.ru", "pwd", expect_pwd_prompt=False) # False

python kw_get.py

False True True False

def tag(name, **kwargs): print(name) print(kwargs) print(type(kwargs)) m = tag('img', src="Malaga.jpg", alt="Malaga Fortress", border=1) print(m)

python tag_func.py

img {'src': 'Malaga.jpg', 'alt': 'Malaga Fortress', 'border': 1} <class 'dict'> None

def tag(name, **attributes): result = '<' + name for key, value in attributes.items(): result += ' {k}="{v}"'.format(k=key, v=str(value)) result += '>' return result m = tag('img', src="Malaga.jpg", alt="Malaga Fortress", border=1) print(m)

<img src="Malaga.jpg" alt="Malaga Fortress" border="1">

Order of Arguments

The order of passing arguments is strictly regulated.

First come the mandatory positional arguments, then *args for optional ones, then the mandatory keyword ones and **kwargs for optional keyword ones, after the optional keyword arguments you can no longer pass mandatory ones.

Wrong:

*args, arg1
arg1, arg2, *args, arg3
**kwargs, arg1
**kwargs, *args
arg1, *args, **kwargs, kwarg1="value"
arg1, *args, kwarg1="value", **kwargs, kwarg2="newValue"

Right:

arg1, arg2, *args, kwarg1="x", kwarg2=1961, **kwargs

def print_args(arg1, arg2, *args, kwarg1, kwarg2): print(arg1) print(arg2) print(args) print(kwarg1) print(kwarg2) print_args(1, 2, 3, 4, 5, kwarg1=6, kwarg2=7)

python order_arg_kwarg.py

1 2 (3, 4, 5) 6 7

It is visible that *args is a tuple. You can extract individual values from it by running

print(args[2])

5

You cannot simply list the arguments, you must pass the last two as keyword arguments

print_args(1, 2, 3, 4, 5, 6, 7)

Traceback (most recent call last): File "/home/andrei/python/order_arg_kwarg.py", line 10, in <module> print_args(1, 2, 3, 4, 5, 6, 7) TypeError: print_args() missing 2 required keyword-only arguments: 'kwarg1' and 'kwarg2'

Thanks to the strict condition on the order of arguments, with the help of * you can forbid passing positional arguments after the mandatory ones.

To do this you need to put * (without args, only *) after the last required mandatory positional argument.

def name_tag(first_name, last_name, *, title=''): print(title, first_name, last_name) name_tag('Eugene', 'Kaspersky', title='Kaspersky founder')

Kaspersky founder Eugene Kaspersky

Now let's try to pass an extra positional argument:

name_tag('Geert', 'Jan', 'Bruinsma', title='Booking.com founder')

Traceback (most recent call last): File "/home/andrei/python/order_arg_kwarg.py", line 21, in <module> name_tag('Geert', 'Jan', 'Bruinsma', title='Booking.com founder') TypeError: name_tag() takes 2 positional arguments but 3 positional arguments (and 1 keyword-only argument) were given

Without * the error would be the following

Traceback (most recent call last): File "/home/andrei/python/order_arg_kwarg.py", line 21, in <module> name_tag('Geert', 'Jan', 'Bruinsma', title='Booking.com founder') TypeError: name_tag() got multiple values for argument 'title'

Positional-Only Arguments

Keyword arguments must be passed as keyword ones, positional ones can be passed as positional and as keyword ones

def add(term1, term2): return term1 + term2 print(add(1, 2)) # A keyword argument must not come before # a positional one. You cannot do: # print(add(term1=1, 2)) # You can do: print(add(1, term2=2)) print(add(term1=1, term2=2))

python positional_only.py

3 3 3

Starting with Python 3.8 you can use / to restrict the use of positional arguments.

If / is added after the positional arguments - they can no longer be passed as keyword ones.

The official documentation is in PEP 570

def add(term1, term2, /): return term1 + term2 print(add(1, 2)) # Now there will be an error: print(add(1, term2=2))

3 Traceback (most recent call last): File "/home/andrei/python/positional_only.py", line 21, in <module> print(add(1, term2=2)) TypeError: add() got some positional-only arguments passed as keyword arguments: 'term2'

This feature is needed to increase compatibility with modules written in C and other languages.

Example

range(start=1, stop=10))

Traceback (most recent call last): File "/home/andrei/python/positional_only.py", line 1, in <module> print(range(start=1, stop=10)) TypeError: range() takes no keyword arguments

Also the ban on the use of keyword arguments guarantees the dependence of the API on names.

Users of your API will not be able to invent their own names and use them, and then complain that something broke.

Extended Call

With the help of * you can make the so-called extended calls (Extended Calls) by collecting positional arguments from iterable objects, such as tuples.

def print_args(arg1, arg2, *args): print(arg1) print(arg2) print(args) t = (11, 12, 13, 14) print_args(*t)

python extended_call.py

11 12 (13, 14)

With the help of ** you can unpack a dictionary and pass it into keyword arguments

def color(red, green, blue, **kwargs): print("r =", red) print("g =", green) print("b =", blue) print(kwargs) k = {'red': 21, "green": 68, 'blue': 120, 'alpha': 52} # Or # k = dict(red=21, green=68, blue=120, alpha=52) color(**k)

r = 21 g = 68 b = 120 {'alpha': 52}

Argument Forwarding

With the help of * and ** you can pass the arguments of one function to another without knowing in advance which set of arguments is contained in the source.

# Argument Forwarding def trace(f, *args, **kwargs): print("args =", args) print("kwargs =", kwargs) result = f(*args, **kwargs) print("result =", result) return result trace(int, "ff", base=16)

args = ('ff',) kwargs = {'base': 16} result = 255

Command-line Arguments

If you are interested in the topic of calling scripts with arguments from the command line - I recommend:

Article author: Andrei Olegovich