The world’s leading publication for data science, AI, and ML professionals.

*args and **kwargs in Python

Discussing the difference between positional and keyword arguments and how to use *args and **kwargs in Python

Photo by Pan Yunbo on Unsplash
Photo by Pan Yunbo on Unsplash

Introduction

In Python, the arguments provided in a function call are passed by assignment (i.e. by object reference). This means that whenever a function gets called, every argument will become a variable pointing to the value that was specified. Obviously, the scope of these variables will be limited to the function being called.

This means that both the function and the caller share the objects by reference, without name aliasing. Therefore, by changing an argument name within the function we should not expect that the caller’s name will get changed, too. However, whenever we mutable objects get changed within a function, we should expect that this is going to have an effect to the caller as well, given that they both share the same object references.

In today’s article we will discuss about the difference between positional and keyword arguments when it comes to writing (or calling) functions in Python. Additionally, we will go through a few examples in order to demonstrate how *args and **kwargs can be used in your code in order to make it more effective and Pythonic.


Subscribe to Data Pipeline, a newsletter dedicated to Data Engineering


Positional vs Keyword arguments in Python

Arguments to Python functions (or methods) can be passed by keyword name or position and the language has the ability to collect both positional and/or keyword arguments in a single call.

Positional arguments refer to the type of arguments that are being parsed based on their positional index that they were provided when the function or method of interest gets called. Therefore, the order that the positional arguments are provided matters because they are matched from left to right.

As an example, let’s consider the built-in Python function [round()](https://docs.python.org/3/library/functions.html#round) that is used to round the input number to the specified _ precision after the decimal point. The definition of the function is round(numbe_r[, _ndigit_s]) which means that an input number is mandatory, whereas the ndigits argument is optional and corresponds to the desired precision.

Now let’s suppose that we have a float variable a = 10.2254 that we want to limit into two decimal point precision. We can call do so by calling the round() function and provide both arguments as positional arguments.

>>> a = 10.2254
>>> round(a, 2)
10.23

Since we provided positional arguments (i.e. we haven’t specified the keyword argument for each of the values provided in the caller), the first positional argument will correspond to number and the second one will correspond to the option ndigits argument, according to the function definition.

On the other hand, keyword arguments are being passed to a function by specifying the keyword in the form of name=value. Therefore, the order these arguments are being passed in the caller doesn’t matter since they are matched by argument name.

Going back to the example with the round() function, we could instead call it by passing keyword arguments.

>>> a = 10.2254
>>> round(number=a, ndigits=2)
10.23

And as mentioned already, the order that we provide keyword arguments doesn’t really matter:

>>> a = 10.2254
>>> round(ndigits=2, number=a)
10.23

Note that we could even combine both positional and keyword arguments where the latter should be specified after the former –

>>> a = 10.2254
>>> round(a, ndigits=2)
10.23

Note however that if you provide a keyword argument before positional arguments, a SyntaxError will be raised.

SyntaxError: positional argument follows keyword argument

Positional arguments and *args

Now let’s suppose that we want to write a function in Python that accepts an arbitrary number of arguments. One option would be to pass the arguments in a collection – say a list – but this won’t be convenient in most of the cases. Additionally, this idea isn’t quite Pythonic – but this is exactly where arbitrary arguments list comes into play.

We could instead take advantage of the *args idiom when defining our function in order to specify that it can actually accept any number of arguments. And it’s actually up to the implementation to properly deal with the provided arguments.

The star * is called the unpacking operator and will return a tuple containing all the arguments provided by the caller.

As an example, let’s consider a fairly simple function that accepts an arbitrary number of integers and returns their sum

def sum_nums(*args):
    sum = 0
    for n in args:
        sum += n
    return sum

Now we can call the above function with literally any number of arguments we may wish:

>>> sum_nums(10, 20)
30
>>> sum_nums(10, 20, 30)
60
>>> sum_nums(5)
5

Note that you can combine normal and arbitrary arguments in the function definition:

def my_func(param, *args):
    ...

Keyword arguments and **kwargs

In the same way, we may sometimes want to write functions that could possibly accept an arbitrary number of keyword arguments.

When unpacking the keyword arguments from **, the result will be a Python dictionary where keys correspond to keyword names and values to the actual argument values provided.

def my_func(**kwargs):
    for key, val in kwargs.items():
        print(key, val)

And now we can call the function with as many keyword arguments as we want to:

>>> my_func(a='hello', b=10)
a hello
b 10
>>> my_func(param1=True, param2=10.5)
param1 True
param2 10.5

Once again, it is useful to mention that arbitrary keyword arguments idiom can be combined with both normal arguments and arbitrary positional arguments:

def my_func(param, *args, **kwargs):
    ...

When to use *args and **kwargs

A very good practical example in which *args and/or **kwargs are usually useful is the decorator. In Python, a decorator is a function that accepts as argument another function, decorates it (i.e. it enriches its functionality) and it finally returns it.

Let’s assume that we want to create a decorator that will be in charge of reporting the execution time of a function to the standard output.

import functools
import time

def execution_time(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(f'{func.__name__} took {end - start}s to run.')
    return wrapper

The wrapper function will simply accept arbitrary positional and keyword arguments that will then be passed in to the function that gets decorated.


Final Thoughts

In today’s article we discussed about positional and keyword arguments and what their main differences are when it comes to writing or calling functions in Python.

Additionally, we discussed about the main difference between the *args and **kwargs and how you can potentially utilize them in your own functions based on what one may want to achieve. Furthermore, we showcased how you can consume positional and keyword arguments from *args and **kwargs in practise.

Finally, we went through a practical example to showcase an application of *args and **kwargs in the context of function wrappers (i.e. decorators).

It is important to mention that the actual notation makes use of star symbols (* and **) and both correspond to arbitrary argument lists. The names *args and **kwargs are nothing more than a convention* (which is quite popular in the community and usually used to describe arbitrary argument lists). Therefore, you do not have to reference them as such – for instance you could even name them as `helloandhey` in your function definition even though I wouldn’t advise you to use such naming convention :).


Subscribe to Data Pipeline, a newsletter dedicated to Data Engineering


You may also like

What is pycache in Python?


What is the Python Global Interpreter Lock (GIL)?


What is Duck Typing in Python?


Related Articles