Introduction
Some functions have no arguments, others have multiple. There are times we have functions with arguments we don't know about beforehand. We may have a variable number of arguments because we want to offer a flexible API to other developers or we don't know the input size. With Python, we can create functions to accept any amount of arguments.
In this article, we will look at how we can define and use functions with variable length arguments. These functions can accept an unknown amount of input, either as consecutive entries or named arguments.
Using Many Arguments with *args
Let's implement a function that finds the minimum value between two numbers. It would look like this:
def my_min(num1, num2):
if num1 < num2:
return num1
return num2
my_min(23, 50)
23
It simply checks if the first number is smaller than the second number. If it is, then the first number is returned. Otherwise, the second number is returned.
If we would like to find a minimum of 3 numbers, we can add another argument to my_min()
and more if-statements. If our minimum function needs to find the lowest number of any indeterminate amount, we can use a list:
def my_min(nums):
result = nums[0]
for num in nums:
if num < result:
result = num
return result
my_min([4, 5, 6, 7, 2])
2
While this works, our coders now have to enclose their numbers in a list, which isn't as straightforward as it was when we had two or three defined arguments. Let's get the best of both worlds with variable length arguments.
Variable-length arguments, varargs for short, are arguments that can take an unspecified amount of input. When these are used, the programmer does not need to wrap the data in a list or an alternative sequence.
In Python, varargs are defined using the *args
syntax. Let's reimplement our my_min()
function with *args
:
def my_min(*args):
result = args[0]
for num in args:
if num < result:
result = num
return result
my_min(4, 5, 6, 7, 2)
2
Note:
args
is just a name, you can name that vararg anything as long as it is preceded by a single asterisk (*
). It's best practice to keep naming itargs
to make it immediately recognizable.
Any argument that comes after *args
must be a named argument - an argument that's referenced by its name instead of its position. With *args
you can no longer reference another argument by its position.
Also, you can only have on *args
type vararg in a function.
You may be thinking that the solution with *args
is very similar to the list solution. That's because *args
is internally a Tuple, which is an iterable sequence similar to lists. If you'd like to verify it's type, you can enter the code in your Python interpreter:
$ python3
>>> def arg_type_test(*args):
... print(type(args))
...
>>> arg_type_test(1, 2)
<class 'tuple'>
With *args
, we can accept multiple arguments in sequence as is done in my_min()
. These arguments are processed by their position. What if we wanted to take multiple arguments, but reference them by their name? We'll take a look at how to do this in the next section.
Using Many Named Arguments with **kwargs
Python can accept multiple keyword arguments, better known as **kwargs
. It behaves similarly to *args
, but stores the arguments in a dictionary instead of tuples:
def kwarg_type_test(**kwargs):
print(kwargs)
kwarg_type_test(a="hi")
kwarg_type_test(roses="red", violets="blue")
The output will be:
{'a': 'hi'}
{'roses': 'red', 'violets': 'blue'}
By using a dictionary, **kwargs
can preserve the names of the arguments, but it would not be able to keep their position.
Note: Like
args
, you can use any other name thankwargs
. However, best practice dictates that you should consistently usekwargs
.
Since **kwargs
is a dictionary, you can iterate over them like any other using the .items()
method:
def kwargs_iterate(**kwargs):
for i, k in kwargs.items():
print(i, '=', k)
kwargs_iterate(hello='world')
When run, our console will show:
hello = world
Keyword arguments are useful when you aren't sure if an argument is going to be available. For example, if we had a function to save a blog post to a database, we would save the information like the content and the author. A blog post may have tags and categories, though those aren't always set.
We can define a function like this:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
def save_blog_post(content, author, tags=[], categories=[]):
pass
Alternatively, we allow the function caller to pass any amount of arguments, and only associate tags
and categories
if they're set:
def save_blog_post(content, author, **kwargs):
if kwargs.get('tags'):
# Save tags with post
pass
if kwargs.get('categories'):
# Save categories with post
pass
Now that we have a grasp of both types of support for variable length arguments, let's see how we can combine the two in one function.
Combining Varargs and Keyword Arguments
Quite often we want to use both *args
and **kwargs
together, especially when writing Python libraries or reusable code. Lucky for us, *args
and **kwargs
play nicely together, and we can use them in the following way:
def combined_varargs(*args, **kwargs):
print(args)
print(kwargs)
combined_varargs(1, 2, 3, a="hi")
If you run that code snippet you'll see:
(1, 2, 3)
{'a': 'hi'}
When mixing the positional and named arguments, positional arguments must come before named arguments. Furthermore, arguments of a fixed length come before arguments with variable length. Therefore, we get an order like this:
- Known positional arguments
*args
- Known named arguments
**kwargs
A function with all types of arguments can look like this:
def super_function(num1, num2, *args, callback=None, messages=[], **kwargs):
pass
Once we follow that order when defining and calling functions with varargs and keyword arguments, we'll get the behavior we expect from them.
So far we've used the *args
and **kwargs
syntax for function definitions. Python allows us to use the same syntax when we call functions as well. Let's see how!
Unpacking Arguments with *args and **kwargs
Let's consider a function add3()
, that accepts 3 numbers and prints their sum. We can create it like this:
def add3(num1, num2, num3):
print("The grand total is", num1 + num2 + num3)
If you had a list of numbers, you can use this function by specifying which list item is used as an argument:
magic_nums = [32, 1, 7]
add3(magic_nums[0], magic_nums[1], magic_nums[2])
If your run this code, you will see:
The grand total is 40
While this works, we can make this more succinct with *args
syntax:
add3(*magic_nums)
The output is The grand total is 40
, just like before.
When we use *args
syntax in a function call, we are unpacking the variable. By unpacking, we mean that we are pulling out the individual values of the list. In this case, we pull out each element of the list and place them in the arguments, where position 0 corresponds to the first argument.
You can also similarly unpack a tuple:
tuple_nums = (32, 1, 7)
add3(*tuple_nums) # The grand total is 40
If you would like the unpack a dictionary, you must use the **kwargs
syntax.
dict_nums = {
'num1': 32,
'num2': 1,
'num3': 7,
}
add3(**dict_nums) # The grand total is 40
In this case, Python matches the dictionary key with the argument name and sets its value.
And that's it! You can easier manage your function calls by unpacking values instead of specifying each argument that needs a value from an object.
Conclusion
With Python, we can use the *args
or **kwargs
syntax to capture a variable number of arguments in our functions. Using *args
, we can process an indefinite number of arguments in a function's position. With **kwargs
, we can retrieve an indefinite number of arguments by their name.
While a function can only have one argument of variable length of each type, we can combine both types of functions in one argument. If we do, we must ensure that positional arguments come before named arguments and that fixed arguments come before those of variable length.
Python allows us to use the syntax for function calls as well. If we have a list or a tuple and use the *args
syntax, it will unpack each value as positional arguments. If we have a dictionary and use **kwargs
syntax, then it will match the names of the dictionary keys with the names of the function arguments.
Are you working on a function that can benefit from these type of arguments? Or maybe you can refactor a function and make it future proof? Let us know what you're working on!