Demystifying Python *args and **kwargs: Advance Python

Demystifying Python *args and **kwargs: Advance Python

Let's get together to find out the answer to this mystical question.

·

13 min read

Before grinding up to learn something, let's get a motivation:

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."

  • Martin Fowler

You might surely see two strange arguments, args and *kwargs, in the Python code. If you ever wondered what these peculiar variables are and why they even exist, then this blog is for you. You’ll learn how to use args and kwargs in Python to add more flexibility to your functions.

By the end of the blog, you’ll know:

  • What the heck do args and *kwargs mean?
  • How to use args and *kwargs in function definitions in Python?
  • How to unpack iterables and dictionaries in Python?

This blog assumes that you know how to define Python functions and work with lists and dictionaries.

1. Arguments


We can pass information into functions as arguments. Arguments are specified after the function name, inside the parentheses. You can add as many arguments as you want; just separate them with a comma.

Let's understand this with a simple example. We have a sum function that takes two arguments, a and b. When the function is called, it returns the arithmetic sum of two numbers (the a and b arguments).

# summation.py
def sum(a, b):
   return a + b

p = sum(1, 2)
q = sum(20, 45)
r = sum(45, 40)

print(f'p = {p}')
print(f'q = {q}')
print(f'r = {r}')

Now, when we run the above code summation.py, we will get the following output:

$ python summation.py
p = 3
q = 65
r = 85

2. Arguments vs Parameters in Python


When making functions, we can make certain inputs to process the instructions included in the function. In this context, we frequently use the terms "arguments" and "parameters" interchangeably to refer to these inputs.

a. Arguments

The arguments are the variables given to the function when it is called. When we call a function, we also pass the variables as an argument to the calling function if it takes them. These are also called actual arguments.

b. Parameters

The parameters are the variables that we can define in the function function signature. In a Python function, the parameters are contained within the parenthesis after the function name. These are also called formal arguments.

Let’s understand this with an example:

# argument.py
def function(parameter1, parameter2): # parameter1 and parameter2 are called parameters.
   print(parameter1)
   print(parameter2)

#calling function
function("Parameter", 123) # "Parameter" and 123 are called arguments.

As we can see from the above example when we define a function, we have two variables, parameter1 and parameter2, inside the parenthesis, which are called parameters, and when we call that function, we pass the two values, "parameter" and 123," which are called arguments.

Now, when we run the above code argument.py, we will get the following output:

$ python argument.py
Parameter
123

Note: By default, a function must be called with the correct number of arguments. Meaning that if your function expects two arguments, you have to call the function with two arguments, not more or less.

For example, This function expects two arguments and gets 2 arguments.

# argument2.py
def two_arg(arg1, arg2):
   print(arg1 + arg2)

two_arg(1, 2)

Now, when we run the above code argument2.py, we will get the following output:

$ python argument2.py
3

If you try to call the function with 1 or 3 arguments, you will get an error:

$ python argument2.py
Traceback (most recent call last):
  File "argument2.py", line 2, in <module>
    two_arg(1)
TypeError: two_arg() missing 1 required positional argument: 'arg2'

3. Types of Arguments in Python


In Python, there are four types of arguments(formal arguments), they are given below:

  • Positional arguments
  • Keyword arguments
  • Default arguments
  • Variable length arguments

Let's understand them one by one.

a. Positional arguments

In Python, a positional argument is a type of argument that is passed to a function based on its position or order in the argument list.

For example, consider the following function that takes two positional arguments, name, and age:

# pos.py
def person(name, age):
   print(name)
   print(age + 2)

When calling this function, you must pass two arguments in the same order as they were defined in the function. For example:

# pos.py
person('Suraj', 21)

This gives the output:

$ python pos.py
Suraj
23

If you call the function below, it throws an error:

$ python pos.py
Traceback (most recent call last):
  File "pos.py", line 3, in <module>
    person(21, 'Suraj')
  File "pos.py", line 4, in-person
    print(age + 2)
TypeError: can only concatenate str (not "int") to str

Positional arguments are useful when you need to pass a small number of arguments to a function and the order of the arguments is important.

b. Keyword arguments

In Python, a keyword argument is a type of argument that is passed to a function using its name rather than its position in the argument list. When calling a function, you can specify keyword arguments by using the syntax argument_name=value.

For example, consider the following function that takes two keyword arguments, length, and breadth:

# find_area.py
def area(length, breadth):
   return length * breadth

When calling this function, you can pass the arguments in any order as long as you specify their names:

# find_area.py
ar = area(length=23, breadth=10)
print(f'Area: {ar}')

ar2 = area(length=2, breadth=10)
print(f'Area: {ar2}')

In this example, the length and breadth arguments are passed as keyword arguments using their names. Also, arguments can be passed in any order. This makes the code more readable and easier to understand, especially if the function has a large number of arguments.

Now, when we run the above code find_area.py, we will get the following output:

$ python argument2.py
Area: 230
Area: 20

Keyword arguments are particularly useful when you need to pass optional arguments to a function or when you need to specify a specific argument out of order concerning other arguments.

c. Default Arguments

In Python, a "default argument" is a type of argument that has a default value assigned to it in the function definition. When defining a function, you can specify a default value for an argument by using the syntax argument_name=default_value.

For example, consider the following function ‘greetings’ that takes one argument, name, which has a default value of Suraj:

# greet.py
def greetings(name="Suraj"):
   print(f'Good morning!, {name}')

You can either call the function by passing the name argument or without passing the argument. If you didn’t pass the argument the function will take a default argument ‘Suraj’ defined in the function declaration.

# greet.py
greetings("Ram") # passing an argument
greetings() # not passing an argument

Now, when we run the above code greet.py, we will get the following output:

$ python greet.py
Good morning!, Ram
Good morning!, Suraj

Default arguments are particularly useful when you have a function with many arguments, and some of those arguments are optional or have commonly used values.

d. Variable length arguments

In Python, variable-length arguments refer to a way of defining a function that can accept an arbitrary number of arguments. This is useful when you do not know how many arguments a function will need to handle in advance.

In Python, there are two ways to define variable-length arguments: args and *kwargs.

4. Using Python *args in a function


The special syntax *args in function definitions in Python is used to pass a variable number of arguments to a function. It is used to pass a non-keyworded, variable-length argument list.

If you want to pass variable-length arguments, one way is to simply pass a list or a set of all the arguments to the function. So for add(), you could pass a list of all the integers you need to add:

# addition.py
def add(integers):
   res = 0
   for x in integers:
       res += x
   return res

list_of_integers = [1, 2, 3, 4, 5]
print(add(list_of_integers))

Now, when we run the above code addition.py, we will get the following output:

$ python addition.py
15

This way works, but whenever you call this function, you’ll also need to create a list of arguments to pass to it. This can be inconvenient, especially if you don’t know all the values that should go into the list upfront.

Now, to save us from this hectic way, *args come to us as a savior because it allows you to pass a varying number of positional arguments in an easy way. Take the following example:

# addition2.py
def add(*args):
   res = 0
   # Iterating over the Python args tuple  (eg: args = (1, 2, 3, 4, 5))
   for x in args:
       res += x
   return res

print(add(1, 2, 3, 4, 5))

In this example, you’re no longer passing a list to the add() function. Instead, you’re passing five different positional arguments. add() takes all the parameters that are provided in the input and packs them all into a single iterable object named args.

Now, when we run the above code addition2.py, we will get the following output:

$ python addition2.py
15

Note: "args" is just a name. You’re not required to use the name args. You can choose any name that you prefer, as long as it contains an unpacking operator (*) in front of it. args is just a convention or best practice for passing variable-length arguments.

For example,

# addition3.py
def add(*integers):
   res = 0
   # Iterating over the Python args tuple  (eg: args = (1, 2, 3, 4, 5))
   for x in integers:
       res += x
   return res

print(add(1, 2, 3, 4, 5))

Now, when we run the above code addition3.py, we will get the following output:

$ python addition3.py
15

Bear in mind that the iterable object you’ll get using the unpacking operator * is not a list but a tuple. A tuple is similar to a list in that they both support slicing and iteration. However, tuples are very different in at least one aspect: lists are mutable while tuples are not.

We can verify it by printing the arg variable.

# addition4.py
def func(*args):
   print(f'args: {args}')

func(1, 2, "ram")

As you can see, we can pass any type of argument like strings, numbers, even tuples, and dictionaries. Another example is

# addition4.py
tuple1 = ("ram", "hari")
dict1 = {"one": 1, "two": 2, "three": 3}

func(tuple1, dict1, "string")

Now, when we run the above code addition4.py, we will get the following output:

$ python addition4.py
args: (1, 2, 'ram')
args: (('ram', 'hari'), {'one': 1, 'two': 2, 'three': 3}, 'string')

5. Using Python **kwargs in a function


*kwargs works just like args, but instead of accepting positional arguments, it accepts keyword (or named) arguments of variable length.

Let's understand this with an example:

# k_wargs.py
def func2(**kwargs):
   print(kwargs)

func2(name="ram", age=22, gender="male")

Now, when we run the above code k_wargs.py, we will get the following output:

$ python k_wargs.py
{'name': 'ram', 'age': 22, 'gender': 'male'}

Note: Note that in the example above, the iterable object **kwargs is a standard Python dictionary. If you iterate over the dictionary and want to return its values, then you must use the values() method of the dictionary object. Let's see this with an example:

# k_wargs2.py
def concat(**kwargs):
   result = ""
   for arg in kwargs.values():
       result += arg
   return result

print(concat(a="Python", b="Rocks", c="Everywhere"))

When you execute the script above, concat() will iterate through the Python kwargs dictionary and concatenate all the values it finds.

$ python k_wargs2.py
PythonRocksEverywhere

Note: Like args, kwargs is just a name that can be changed to whatever you want. Again, what is important here is the use of the unpacking operator (**).

So, the previous example could be written like this:

# k_wargs3.py
def concat(**strings):
   result = ""
   for arg in strings.values():
       result += arg
   return result

print(concat(a="Python", b="Rocks", c="Everywhere"))

Now, when we run the above code k_wargs3.py, we will get the following output:

$ python k_wargs3.py
PythonRocksEverywhere

Note: We can also define both args and kwargs in the same function. Let's see the example:

# k_wargs4.py
def foo(required, *args, **kwargs):
   print(required)
   if args:
       print(args)
   if kwargs:
       print(kwargs)

foo('hello') #print hello
foo('hello', 1, 2, 3, 4) #print hello and args tuple
foo('hello', 1, 2, 3, 4, key1='value', key2='400') #print hello, args tuple and kwargs dictionary.

Now, when we run the above code k_wargs4.py, we will get the following output:

$ python k_wargs4.py
hello
hello
(1, 2, 3, 4)
hello
(1, 2, 3, 4)
{'key1': 'value', 'key2': '400'}

args and *kwargs are also used in subclassing:

# k_wargs5.py
class Car:
    def __init__(self, color, mileage):
        self.color = color
        self.mileage = mileage

class AlwaysBlueCar(Car):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.color = 'blue'

a = AlwaysBlueCar("Red", 120)
print(a.color)

6. Order of Arguments in a Function


Just like non-default arguments have to come before default arguments in a function, args must come before *kwargs. Ultimately, the correct order for your parameter is:

  • Standard arguments
  • *args arguments
  • **kwargs arguments

For example, this function definition is correct:

def foo(a, b, *args, **kwargs):
   pass

Now, what happens if we write *kwargs and then args in a function definition:

# wrong_function_definition.py
def bar(a, b, **kwargs, *args):
   pass

If you try to run this example, you’ll receive an error from the interpreter. It will throw a syntax error. Because the ordering of arguments is not correct.

$ python wrong_function_definition.py
 File "wrong_function_definition.py", line 2
    def my_function(a, b, **kwargs, *args):
                                    ^
SyntaxError: invalid syntax

7. Unpacking with the Unpacking Operators


The unpacking operators are operators that unpack the values from iterable objects in Python. The single asterisk operator can be used on any iterable that Python provides, while the double asterisk operator * can only be used on dictionaries.

Let's demonstrate this with an example:

# print_list.py
list1 = [1, 2, 3]
print(list1)

This code defines a list and then prints it to the standard output:

$ python print_list.py
[1, 2, 3]

You can closely observe the output, the output is a list of numbers contained within the big brackets.

Now, try to prepend the unpacking operator * to list1:

# print_unpacked_list.py
list1 = [1, 2, 3]
print(*list1)

Here, the * operator tells print() to unpack the list first. In this case, the output is no longer the list itself, but rather the content of the list:

$ python print_unpacked_list.py
1 2 3

Instead of a list, print() has taken three separate arguments as the input.

# test.py
def test(a, b, c):
   return a+b+c
list2 = [2, 4, 5]
print(test(*list2))

Now, when we run the above code test.py, we will get the following output:

$ python test.py
11

When you use the * operator to unpack a list and pass arguments to a function, it’s exactly as though you’re passing every single argument alone. This means that you can use multiple unpacking operators to get values from several lists and pass them all to a single function.

To test this behavior, consider the following example:

# unpack_addition.py
def addition(*args):
   res = 0
   for x in args:
       res += x
   return res

list1 = [1, 2, 3]
list2 = [4, 5]
list3 = [6, 7, 8, 9]

print(addition(*list1, *list2, *list3))

If you run this example, all three lists are unpacked. Each individual item is passed to addition(), resulting in the sum of all numbers inside the list:

$ python unpack_addition.py
45

You can further explore the unpacking in the Python official documentation.

8. Conclusion


After acquiring knowledge about args and *kwargs, you can now employ them to handle a varying number of arguments in your functions. Additionally, you have gained insight into the unpacking operators.

Specifically, you have gained knowledge of:

  • The significance of args and *kwargs
  • The procedure for utilizing args and *kwargs in defining functions
  • The method of utilizing a solitary asterisk (*) to unpack iterables
  • The technique of utilizing two asterisks (**) to unpack dictionaries

If you still have questions, don’t hesitate to reach out in the comments section below! To learn more about the use of asterisks in Python, have a look at link Trey Hunner’s article on the subject.

This much for today; see you in the next blog. Take care! Cheers!