*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:
- *args - for positional arguments
- **kwargs - for keyword arguments
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