In the first three articles in this Series, you familiarised yourself with the key terms when dealing with functions. You also explored positional and keyword arguments and optional arguments with default values. In this article, you’ll look at different types of optional arguments. Rather unfortunately, these are often referred to by the obscure names args
and kwargs
in Python.
Overview Of The Intermediate Python Functions Series
Here’s an overview of the seven articles in this series:
- Introduction to the series: Do you know all your functions terminology well?
- Choosing whether to use positional or keyword arguments when calling a function
- Using optional arguments by including default values when defining a function
- [This article] Using any number of optional positional and keyword arguments:
*args
and**kwargs
- Using positional-only arguments and keyword-only arguments: the “rogue” forward slash / or asterisk * in function signatures
- Type hinting in functions
- Best practices when defining and using functions
Using Any Number of Optional Positional Arguments: *args
The topic of *args
may seem weird and difficult. However, it’s neither that strange nor that hard. The name is a bit off-putting, but once you understand what’s going on, *args
will make sense.
Let’s dive in by looking at this code:
def greet_people(number, *people): for person in people: print(f"Hello {person}! How are you doing today?\n" * number)
Did you spot the asterisk in front of the parameter name people
? You’ll often see the parameter name *args
used for this, such as:
def greet_person(number, *args):
However, what makes this “special” is not the name ‘args’ but the asterisk *
in front of the parameter name. You can use any parameter name you want. In fact, it’s best practice to use parameter names that describe the data rather than obscure terms. This is why I chose people
in this example.
Let’s go back to the function you defined earlier. Consider the following three function calls:
# 1. greet_people(3, "James", "Stephen", "Kate") # 2. greet_people(2, "Bob") # 3. greet_people(5)
All three function calls are valid:
- The first function call has four arguments:
3
,"James"
,"Stephen"
, and"Kate"
- The second function call has two arguments:
2
and"Bob"
- The last function call has one argument:
5
To understand how this is possible, let’s dig a bit deeper into what the parameter people
is. Let’s print it out and also print out its type. I’ll comment out the rest of the function’s body for the time being. I’m showing the output from the three function calls as comments in the code snippet below:
def greet_people(number, *people): print(people) print(type(people)) # for person in people: # print(f"Hello {person}! How are you doing today?\n" * number) # 1. greet_people(3, "James", "Stephen", "Kate") # OUTPUT: # ('James', 'Stephen', 'Kate') # <class 'tuple'> # 2. greet_people(2, "Bob") # OUTPUT: # ('Bob',) # <class 'tuple'> # 3. greet_people(5) # OUTPUT: # () # <class 'tuple'>
The local variable people
inside the function is a tuple. Its contents are all the arguments you used in the function calls from the second argument onward. The first argument when you call greet_people()
is the positional argument assigned to the first parameter: number
. All the remaining arguments are collected in the tuple named people
.
In the first function call, the first argument is the integer 3
. Then there are three more arguments: "James"
, "Stephen"
, and "Kate"
. Therefore, people
is a tuple containing these three strings.
In the second function call, the required positional argument is 2
. Then, there’s only one additional argument: "Bob"
. Therefore, the tuple people
contains just one item.
In the final function call, there are no additional arguments. The only argument is the required one which is assigned to number
. Therefore, people
is an empty tuple. It’s empty, but it still exists!
Let’s go back to the function definition you wrote at the start of this section and look at the output from the three function calls:
def greet_people(number, *people): for person in people: print(f"Hello {person}! How are you doing today?\n" * number) # 1. greet_people(3, "James", "Stephen", "Kate") # 2. greet_people(2, "Bob") # 3. greet_people(5)
The output from this code is:
Hello James! How are you doing today? Hello James! How are you doing today? Hello James! How are you doing today? Hello Stephen! How are you doing today? Hello Stephen! How are you doing today? Hello Stephen! How are you doing today? Hello Kate! How are you doing today? Hello Kate! How are you doing today? Hello Kate! How are you doing today? Hello Bob! How are you doing today? Hello Bob! How are you doing today?
Since people
is a tuple, you can iterate through it using a for
loop. This way, you can perform the same action for each of the optional arguments assigned to the tuple people
.
The first function call prints three blocks of output (the ones with James, Stephen, and Kate.) The second function call outputs the lines with Bob in them. The final function call doesn’t print anything since the tuple people
is empty.
Therefore, when you add an *args
to your function definition, you’re allowing any number of optional positional arguments to be added to the function call. Note that I used the term ‘positional’ in the last sentence. These arguments are collected into the args
variable using their position in the function call. All the arguments that come after the required positional arguments are optional positional arguments.
Some rules when using *args
Let’s make a small change to the function definition from earlier:
def greet_people(*people, number): for person in people: print(f"Hello {person}! How are you doing today?\n" * number)
You’ve swapped the position of the parameters number
and *people
compared to the previous example. Let’s try this function call:
greet_people("James", "Kate", 5)
This raises the following error:
Traceback (most recent call last): File "...", line 5, in <module> greet_people("James", "Kate", 5) TypeError: greet_people() missing 1 required keyword-only argument: 'number'
Note that the error is not raised by the function definition but by the function call. There’s a hint as to what the problem is in the error message. Let’s summarise the problem, and then I’ll expand further. All parameters which follow the *args
parameter in the function definition must be used with keyword arguments.
What? And Why?
The parameter *people
tells the function that it can accept any number of arguments which will be assigned to the tuple people
. Since this could be any number, there’s no way for the program to know when you wish to stop adding these optional arguments and move on to arguments that are assigned to the next parameters, in this case, number
.
Let’s fix the function call, and then we’ll come back to this explanation:
def greet_people(*people, number): for person in people: print(f"Hello {person}! How are you doing today?\n" * number) greet_people("James", "Kate", number=5)
This code now works and gives the following output:
Hello James! How are you doing today? Hello James! How are you doing today? Hello James! How are you doing today? Hello James! How are you doing today? Hello James! How are you doing today? Hello Kate! How are you doing today? Hello Kate! How are you doing today? Hello Kate! How are you doing today? Hello Kate! How are you doing today? Hello Kate! How are you doing today?
Since the last argument is a named (keyword) argument, it’s no longer ambiguous that the value 5
should be assigned to the parameter name number
. The program can’t read your mind! Therefore, it needs to be told when the optional positional arguments end since you can have any number of them. Naming all subsequent arguments removes all ambiguity and fixes the problem.
*args
summary
Before moving on, let’s summarise what we’ve learned about *args
.
- You can add a parameter with an asterisk
*
in front of it when defining a function to show that you can use any number of positional arguments in the function call. You can use none, one, or more arguments matched to the*args
parameter - All the arguments which match the
*args
parameter are collected in a tuple - There’s nothing special about the name
args
. You can (in fact, you should) use a more descriptive parameter name in your code. Just add the asterisk*
before the parameter name
Using Any Number of Optional Keyword Arguments: **kwargs
When you hear about *args
in Python, you’ll often hear them mentioned in the same breath as **kwargs
. They always seem to come as a pair: “Args and Kwargs in Python!” So let’s see what **kwargs
are with the following example:
def greet_people(**people): for person, number in people.items(): print(f"Hello {person}! How are you doing today?\n" * number)
As with ‘args’, there’s nothing special about the name ‘kwargs’. What makes a kwargs a kwargs (!) is the double asterisk **
in front of the parameter name. You can use a more descriptive parameter name when defining a function with **kwargs
.
Let’s use the following three function calls as examples in this section:
# 1. greet_people(James=5, Mark=2, Ishaan=1) # 2. greet_people(Stephen=4) # 3. greet_people()
Let’s explore the variable people
inside the function. As you did earlier, you’ll print out its contents and its type. The rest of the function body is commented, out and the output from the three function calls is shown as comments:
def greet_people(**people): print(people) print(type(people)) # for person, number in people.items(): # print(f"Hello {person}! How are you doing today?\n" * number) # 1. greet_people(James=5, Mark=2, Ishaan=1) # OUTPUT: # {'James': 5, 'Mark': 2, 'Ishaan': 1} # <class 'dict'> # 2. greet_people(Stephen=4) # OUTPUT: # {'Stephen': 4} # <class 'dict'> # 3. greet_people() # OUTPUT: # {} # <class 'dict'>
The variable people
is a dictionary. You used keyword (named) arguments in the function calls, not positional ones. Notice how the keywords you used when naming the arguments are the same as the keys in the dictionary. The argument is the value associated with that key.
For example, in the function call greet_people(James=5, Mark=2, Ishaan=1)
, the keyword argument James=5
became an item in the dictionary with the string "James"
as key and 5
as its value, and so on for the other named arguments. You can include as many keyword arguments as you wish when you call a function with **kwargs
.
You may be wondering where the name ‘kwargs’ comes from. Possibly you guessed this already: KeyWord ARGumentS.
Here’s the original function definition and the three function calls again:
def greet_people(**people): for person, number in people.items(): print(f"Hello {person}! How are you doing today?\n" * number) # 1. greet_people(James=5, Mark=2, Ishaan=1) # 2. greet_people(Stephen=4) # 3. greet_people()
This code gives the following output:
Hello James! How are you doing today? Hello James! How are you doing today? Hello James! How are you doing today? Hello James! How are you doing today? Hello James! How are you doing today? Hello Mark! How are you doing today? Hello Mark! How are you doing today? Hello Ishaan! How are you doing today? Hello Stephen! How are you doing today? Hello Stephen! How are you doing today? Hello Stephen! How are you doing today? Hello Stephen! How are you doing today?
Since people
is a dictionary, you can loop through it using the dictionary method items()
. The first function call prints out three sets of greetings for James, Mark, and Ishaan. The number of times each greeting is printed depends on the argument used. The second call displays four greetings for Stephen. The final call doesn’t display anything since the dictionary is empty.
**kwargs summary
In summary:
- You can add a parameter with a double asterisk
**
in front of it when defining a function to show that you can use any number of keyword arguments in the function call - All the arguments which match the
**kwargs
parameter are collected in a dictionary - The keyword becomes the key in the dictionary. The argument becomes the value associated with that key
- There’s nothing special about the name
kwargs
. As long as you add the double asterisk**
before the parameter name, you can use a more descriptive name
Combining *args
and **kwargs
You now know about *args
. You also know about **kwargs
. Let’s combine both args
and kwargs
in Python functions.
Let’s look at this code. You have two teams (represented using the dictionaries red_team
and blue_team
) and the function adds members to one of the teams. Each member starts off with some number of points:
red_team = {} blue_team = {} def add_team_members(team, **people): for person, points in people.items(): team[person] = points add_team_members(red_team, Stephen=10, Kate=8, Sharon=12) print(f"{red_team = }") print(f"{blue_team = }")
What do you think the output will be?
The function definition has two parameters. The second one has the double asterisk **
in front of it, which makes it a ‘kwargs’. This means you can pass any number of keyword arguments which will be assigned to a dictionary with the name people
.
Now, let’s look at the function call. There is one positional argument, red_team
. There are also three keyword arguments. Remember that you can have as many keyword arguments as you want after the first required positional argument.
In the function definition’s body, people
is a dictionary. Therefore, you can loop through it using items()
. The variables person
and points
will contain the key and the value of each dictionary item. In the first iteration of the for
loop, person
will contain the string "Stephen"
and points
will contain 10
. In the second for
loop iteration, person
will contain "Kate"
and points
will be 8
. And "Sharon"
and 12
will be used in the third loop iteration.
Here’s the output of the code above:
red_team = {'Stephen': 10, 'Kate': 8, 'Sharon': 12} blue_team = {}
Only red_team
has changed. blue_team
is still the same empty dictionary you initialised at the beginning. That’s because you passed red_team
as the first argument in add_team_members()
.
Some rules when using *args
and **kwargs
As you’ve seen in the previous articles in this series and earlier in this one, there are always some rules on how to order the different types of parameters and arguments. Let’s look at a few of these rules here.
Let’s start with this example:
red_team = {} blue_team = {} def add_team_members(**people, team): for person, points in people.items(): team[person] = points
You’ll get an error when you run this code even without a function call:
File "...", line 4 def add_team_members(**people, team): ^^^^ SyntaxError: arguments cannot follow var-keyword argument
Note: the error message in Python versions before 3.11 is different. The error says that the variable keyword parameter – that’s the **kwargs
– must come after the other parameters.
Let’s make a change to the function before you explore some other options. You can check whether the name of the team member is already in the team and only add it if it’s not already there:
red_team = {"Stephen": 4} blue_team = {} def add_team_members(team, **people): for person, points in people.items(): if person not in team.keys(): team[person] = points else: print(f"{person} is already in the team") add_team_members(red_team, Stephen=10, Kate=8, Sharon=12) print(f"{red_team = }") print(f"{blue_team = }")
The output from this code is:
Stephen is already in the team red_team = {'Stephen': 4, 'Kate': 8, 'Sharon': 12} blue_team = {}
Notice how "Stephen"
is already in the dictionary with a value of 4
so the function doesn’t update it. Now, you can add another team and modify the function so that you can add team members to more than one team at a time in a single function call. People can be in more than one team:
red_team = {} blue_team = {} green_team = {} def add_team_members(*teams, **people): for person, points in people.items(): for team in teams: if person not in team.keys(): team[person] = points else: print(f"{person} is already in the team.") add_team_members(red_team, blue_team, Stephen=10, Kate=8, Sharon=12) add_team_members(red_team, green_team, Mary=3, Trevor=15) add_team_members(blue_team, Ishaan=8) print(f"{red_team = }") print(f"{blue_team = }") print(f"{green_team = }")
You’re using both *args
and **kwargs
in this function. When you call the function, you can first use any number of positional arguments (without a keyword) followed by any number of keyword arguments:
- The positional arguments are assigned to the tuple
teams
- The keyword arguments are assigned to the dictionary
people
The output from this code is:
red_team = {'Stephen': 10, 'Kate': 8, 'Sharon': 12, 'Mary': 3, 'Trevor': 15} blue_team = {'Stephen': 10, 'Kate': 8, 'Sharon': 12, 'Ishaan': 8} green_team = {'Mary': 3, 'Trevor': 15}
You’ll note that Stephen, Kate, and Sharon are in both the red team and the blue team. Mary and Trevor are in the red and green teams. Ishaan is just in the blue team.
Let’s get back to talking about the rules of what you can and cannot do. You can change the function call from the one you used earlier:
red_team = {} blue_team = {} green_team = {} def add_team_members(*teams, **people): for person, points in people.items(): for team in teams: if person not in team.keys(): team[person] = points else: print(f"{person} is already in the team.") add_team_members(Stephen=10, Kate=8, Sharon=12, red_team, blue_team)
The output from this code is the following error:
File "...", line 13 add_team_members(Stephen=10, Kate=8, Sharon=12, red_team, blue_team) ^ SyntaxError: positional argument follows keyword argument
You cannot place keyword (named) arguments before positional arguments when you call the function. This makes sense since *teams
is listed before **people
in the function signature. So, can you swap these over when you define a function? Let’s find out:
red_team = {} blue_team = {} green_team = {} def add_team_members(**people, *teams): for person, points in people.items(): for team in teams: if person not in team.keys(): team[person] = points else: print(f"{person} is already in the team.")
The answer is “No”:
File "...", line 5 def add_team_members(**people, *teams): ^ SyntaxError: arguments cannot follow var-keyword argument
This is an error with the function definition, not the function call (there is no function call in this code!) Therefore, you must include the *args
before the **kwargs
when you define a function.
Final Words
There are more combinations of “normal” positional arguments, “normal” keyword arguments, *args
, and **kwargs
you could try. But we’ll draw a line here in this article as the main objective was to give you a good idea of what these types of arguments are and how you can use them.
Now that you know about args and kwargs in Python functions, you can move on to yet another type of argument. In the next article, you’ll read about positional-only arguments and keyword-only arguments.
Next Article:<Link will be posted here when the next article in the series is posted>
Further Reading
- Chapter 3: Power-up Your Coding: Create Your Own Functions for an in-depth introduction to Python functions
- Chapter 6: Functions Revisited. This chapter covers topics that will be dealt with later on in the series
- The White Room: Understanding Programming. In this article, I briefly referred to parameters as boxes which store data. This is part of a broader analogy I like to use. You can read more in this chapter
- Using Python Optional Arguments When Defining Functions is an article I wrote for Real Python if you want to read ahead.
Get the latest blog updates
No spam promise. You’ll get an email when a new blog post is published
The post Argh! What are args and kwargs in Python? [Intermediate Python Functions Series #4] appeared first on The Python Coding Book.