Using Python Optional Arguments When Defining Functions

Using Python Optional Arguments When Defining Functions

by Stephen Gruppetta Aug 30, 2021 basics python

Defining your own functions is an essential skill for writing clean and effective code. In this tutorial, you’ll explore the techniques you have available for defining Python functions that take optional arguments. When you master Python optional arguments, you’ll be able to define functions that are more powerful and more flexible.

In this tutorial, you’ll learn:

  • What the difference is between parameters and arguments
  • How to define functions with optional arguments and default parameter values
  • How to define functions using args and kwargs
  • How to deal with error messages about optional arguments

To get the most out of this tutorial, you’ll need some familiarity with defining functions with required arguments.

Creating Functions in Python for Reusing Code

You can think of a function as a mini-program that runs within another program or within another function. The main program calls the mini-program and sends information that the mini-program will need as it runs. When the function completes all of its actions, it may send some data back to the main program that has called it.

The primary purpose of a function is to allow you to reuse the code within it whenever you need it, using different inputs if required.

When you use functions, you are extending your Python vocabulary. This lets you express the solution to your problem in a clearer and more succinct way.

In Python, by convention, you should name a function using lowercase letters with words separated by an underscore, such as do_something(). These conventions are described in PEP 8, which is Python’s style guide. You’ll need to add parentheses after the function name when you call it. Since functions represent actions, it’s a best practice to start your function names with a verb to make your code more readable.

Defining Functions With No Input Parameters

In this tutorial, you’ll use the example of a basic program that creates and maintains a shopping list and prints it out when you’re ready to go to the supermarket.

Start by creating a shopping list:

shopping_list = {
    "Bread": 1,
    "Milk": 2,
    "Chocolate": 1,
    "Butter": 1,
    "Coffee": 1,
}

You’re using a dictionary to store the item name as the key and the quantity you need to buy of each item as the value. You can define a function to display the shopping list:

# optional_params.py

shopping_list = {
    "Bread": 1,
    "Milk": 2,
    "Chocolate": 1,
    "Butter": 1,
    "Coffee": 1,
}

def show_list():
    for item_name, quantity in shopping_list.items():
        print(f"{quantity}x {item_name}")

show_list()

When you run this script, you’ll get a printout of the shopping list:

$ python optional_params.py
1x Bread
2x Milk
1x Chocolate
1x Butter
1x Coffee

The function you’ve defined has no input parameters as the parentheses in the function signature are empty. The signature is the first line in the function definition:

def show_list():

You don’t need any input parameters in this example since the dictionary shopping_list is a global variable. This means that it can be accessed from everywhere in the program, including from within the function definition. This is called the global scope. You can read more about scope in Python Scope & the LEGB Rule: Resolving Names in Your Code.

Using global variables in this way is not a good practice. It can lead to several functions making changes to the same data structure, which can lead to bugs that are hard to find. You’ll see how to improve on this later on in this tutorial when you’ll pass the dictionary to the function as an argument.

In the next section, you’ll define a function that has input parameters.

Defining Functions With Required Input Arguments

Instead of writing the shopping list directly in the code, you can now initialize an empty dictionary and write a function that allows you to add items to the shopping list:

# optional_params.py

shopping_list = {}

# ...

def add_item(item_name, quantity):
    if item_name in shopping_list.keys():
        shopping_list[item_name] += quantity
    else:
        shopping_list[item_name] = quantity

add_item("Bread", 1)
print(shopping_list)

The function iterates through the dictionary’s keys, and if the key exists, the quantity is increased. If the item is not one of the keys, the key is created and a value of 1 is assigned to it. You can run this script to show the printed dictionary:

$ python optional_params.py
{'Bread': 1}

You’ve included two parameters in the function signature:

  1. item_name
  2. quantity

Parameters don’t have any values yet. The parameter names are used in the code within the function definition. When you call the function, you pass arguments within the parentheses, one for each parameter. An argument is a value you pass to the function.

The distinction between parameters and arguments can often be overlooked. It’s a subtle but important difference. You may sometimes find parameters referred to as formal parameters and arguments as actual parameters.

The arguments you input when calling add_item() are required arguments. If you try to call the function without the arguments, you’ll get an error:

# optional_params.py

shopping_list = {}

def add_item(item_name, quantity):
    if item_name in shopping_list.keys():
        shopping_list[item_name] += quantity
    else:
        shopping_list[item_name] = quantity

add_item()
print(shopping_list)

The traceback will give a TypeError stating that the arguments are required:

$ python optional_params.py
Traceback (most recent call last):
  File "optional_params.py", line 11, in <module>
    add_item()
TypeError: add_item() missing 2 required positional arguments: 'item_name' and 'quantity'

You’ll look at more error messages related to using the wrong number of arguments, or using them in the wrong order, in a later section of this tutorial.

Using Python Optional Arguments With Default Values

In this section, you’ll learn how to define a function that takes an optional argument. Functions with optional arguments offer more flexibility in how you can use them. You can call the function with or without the argument, and if there is no argument in the function call, then a default value is used.

Default Values Assigned to Input Parameters

You can modify the function add_item() so that the parameter quantity has a default value:

# optional_params.py

shopping_list = {}

def add_item(item_name, quantity=1):
    if item_name in shopping_list.keys():
        shopping_list[item_name] += quantity
    else:
        shopping_list[item_name] = quantity

add_item("Bread")
add_item("Milk", 2)
print(shopping_list)

In the function signature, you’ve added the default value 1 to the parameter quantity. This doesn’t mean that the value of quantity will always be 1. If you pass an argument corresponding to quantity when you call the function, then that argument will be used as the value for the parameter. However, if you don’t pass any argument, then the default value will be used.

Parameters with default values can’t be followed by regular parameters. You’ll read more about the order in which you can define parameters later in this tutorial.

The function add_item() now has one required parameter and one optional parameter. In the code example above, you call add_item() twice. Your first function call has a single argument, which corresponds to the required parameter item_name. In this case, quantity defaults to 1. Your second function call has two arguments, so the default value isn’t used in this case. You can see the output of this below:

$ python optional_params.py
{'Bread': 1, 'Milk': 2}

You can also pass required and optional arguments into a function as keyword arguments. Keyword arguments can also be referred to as named arguments:

add_item(item_name="Milk", quantity=2)

You can now revisit the first function you defined in this tutorial and refactor it so that it also accepts a default argument:

def show_list(include_quantities=True):
    for item_name, quantity in shopping_list.items():
        if include_quantities:
            print(f"{quantity}x {item_name}")
        else:
            print(item_name)

Now when you use show_list(), you can call it with no input arguments or pass a Boolean value as a flag argument. If you don’t pass any arguments when calling the function, then the shopping list is displayed by showing each item’s name and quantity. The function will display the same output if you pass True as an argument when calling the function. However, if you use show_list(False), only the item names are displayed.

You should avoid using flags in cases where the value of the flag alters the function’s behavior significantly. A function should only be responsible for one thing. If you want a flag to push the function into an alternative path, you may consider writing a separate function instead.

Common Default Argument Values

In the examples you worked on above, you used the integer 1 as a default value in one case and the Boolean value True in the other. These are common default values you’ll find in function definitions. However, the data type you should use for default values depends on the function you’re defining and how you want the function to be used.

The integers 0 and 1 are common default values to use when a parameter’s value needs to be an integer. This is because 0 and 1 are often useful fallback values to have. In the add_item() function you wrote earlier, setting the quantity for a new item to 1 is the most logical option.

However, if you had a habit of buying two of everything you purchase when you go to the supermarket, then setting the default value to 2 may be more appropriate for you.

When the input parameter needs to be a string, a common default value to use is the empty string (""). This assigns a value whose data type is string but doesn’t put in any additional characters. You can modify add_item() so that both arguments are optional:

def add_item(item_name="", quantity=1):
    if item_name in shopping_list.keys():
        shopping_list[item_name] += quantity
    else:
        shopping_list[item_name] = quantity

You have modified the function so that both parameters have a default value and therefore the function can be called with no input parameters:

add_item()

This line of code will add an item to the shopping_list dictionary with an empty string as a key and a value of 1. It’s fairly common to check whether an argument has been passed when the function is called and run some code accordingly. You can change the above function to do this:

def add_item(item_name="", quantity=1):
    if not item_name:
        quantity = 0
    if item_name in shopping_list.keys():
        shopping_list[item_name] += quantity
    else:
        shopping_list[item_name] = quantity

In this version, if no item is passed to the function, the function sets the quantity to 0. The empty string has a falsy value, which means that bool("") returns False, whereas any other string will have a truthy value. When an if keyword is followed by a truthy or falsy value, the if statement will interpret these as True or False. You can read more about truthy and falsy values in Python Booleans: Optimize Your Code With Truth Values.

Therefore, you can use the variable directly within an if statement to check whether an optional argument was used.

Another common value that’s often used as a default value is None. This is Python’s way of representing nothing, although it is actually an object that represents the null value. You’ll see an example of when None is a useful default value to use in the next section.

Data Types That Shouldn’t Be Used as Default Arguments

You’ve used integers and strings as default values in the examples above, and None is another common default value. These are not the only data types you can use as default values. However, not all data types should be used.

In this section, you’ll see why mutable data types should not be used as default values in function definitions. A mutable object is one whose values can be changed, such as a list or a dictionary. You can find out more about mutable and immutable data types in Immutability in Python and in Python’s official documentation.

You can add the dictionary containing the item names and quantities as an input parameter to the function you defined earlier. You can start by making all arguments required ones:

def add_item(item_name, quantity, shopping_list):
    if item_name in shopping_list.keys():
        shopping_list[item_name] += quantity
    else:
        shopping_list[item_name] = quantity

    return shopping_list

You can now pass shopping_list to the function when you call it. This makes the function more self-contained as it doesn’t rely on a variable called shopping_list to exist in the scope that’s calling the function. This change also makes the function more flexible as you can use it with different input dictionaries.

You’ve also added the return statement to return the modified dictionary. This line is technically not required at this stage as dictionaries are a mutable data type and therefore the function will change the state of the dictionary that exists in the main module. However, you’ll need the return statement later when you make this argument optional, so it’s best to include it now.

To call the function, you’ll need to assign the data returned by the function to a variable:

shopping_list = add_item("Coffee", 2, shopping_list)

You can also add a shopping_list parameter to show_list(), the first function you defined in this tutorial. You can now have several shopping lists in your program and use the same functions to add items and display the shopping lists:

# optional_params.py

hardware_store_list = {}
supermarket_list = {}

def show_list(shopping_list, include_quantities=True):
    print()
    for item_name, quantity in shopping_list.items():
        if include_quantities:
            print(f"{quantity}x {item_name}")
        else:
            print(item_name)

def add_item(item_name, quantity, shopping_list):
    if item_name in shopping_list.keys():
        shopping_list[item_name] += quantity
    else:
        shopping_list[item_name] = quantity

    return shopping_list

hardware_store_list = add_item("Nails", 1, hardware_store_list)
hardware_store_list = add_item("Screwdriver", 1, hardware_store_list)
hardware_store_list = add_item("Glue", 3, hardware_store_list)

supermarket_list = add_item("Bread", 1, supermarket_list)
supermarket_list = add_item("Milk", 2, supermarket_list)

show_list(hardware_store_list)
show_list(supermarket_list)

You can see the output of this code below. The list of items to buy from the hardware store is shown first. The second part of the output shows the items needed from the supermarket:

$ python optional_params.py

1x Nails
1x Screwdriver
3x Glue

1x Bread
2x Milk

You’ll now add a default value for the parameter shopping_list in add_item() so that if no dictionary is passed to the function, then an empty dictionary is used. The most tempting option is to make the default value an empty dictionary. You’ll see why this is not a good idea soon, but you can try this option for now:

# optional_params.py

def show_list(shopping_list, include_quantities=True):
    print()
    for item_name, quantity in shopping_list.items():
        if include_quantities:
            print(f"{quantity}x {item_name}")
        else:
            print(item_name)

def add_item(item_name, quantity, shopping_list={}):
    if item_name in shopping_list.keys():
        shopping_list[item_name] += quantity
    else:
        shopping_list[item_name] = quantity

    return shopping_list

clothes_shop_list = add_item("Shirt", 3)
show_list(clothes_shop_list)

When you run this script, you’ll get the output below showing the items needed from the clothes shop, which may give the impression that this code works as intended:

$ python optional_params.py

3x Shirt

However, this code has a serious flaw that can lead to unexpected and wrong results. You can add a new shopping list for items needed from the electronics store by using add_item() with no argument corresponding to shopping_list. This leads to the default value being used, which you’d hope would create a new empty dictionary:

# optional_params.py

def show_list(shopping_list, include_quantities=True):
    print()
    for item_name, quantity in shopping_list.items():
        if include_quantities:
            print(f"{quantity}x {item_name}")
        else:
            print(item_name)

def add_item(item_name, quantity, shopping_list={}):
    if item_name in shopping_list.keys():
        shopping_list[item_name] += quantity
    else:
        shopping_list[item_name] = quantity

    return shopping_list

clothes_shop_list = add_item("Shirt", 3)
electronics_store_list = add_item("USB cable", 1)
show_list(clothes_shop_list)
show_list(electronics_store_list)

You’ll see the problem when you look at the output from this code:

$ python optional_params.py

3x Shirt
1x USB cable

3x Shirt
1x USB cable

Both shopping lists are identical even though you assigned the output from add_item() to different variables each time you called the function. The problem happens because dictionaries are a mutable data type.

You assigned an empty dictionary as the default value for the parameter shopping_list when you defined the function. The first time you call the function, this dictionary is empty. However, as dictionaries are a mutable type, when you assign values to the dictionary, the default dictionary is no longer empty.

When you call the function the second time and the default value for shopping_list is required again, the default dictionary is no longer empty as it was populated the first time you called the function. Since you’re calling the same function, you’re using the same default dictionary stored in memory.

This behavior doesn’t happen with immutable data types. The solution to this problem is to use another default value, such as None, and then create an empty dictionary within the function when no optional argument is passed:

# optional_params.py

def show_list(shopping_list, include_quantities=True):
    print()
    for item_name, quantity in shopping_list.items():
        if include_quantities:
            print(f"{quantity}x {item_name}")
        else:
            print(item_name)

def add_item(item_name, quantity, shopping_list=None):
    if shopping_list is None:
        shopping_list = {}
    if item_name in shopping_list.keys():
        shopping_list[item_name] += quantity
    else:
        shopping_list[item_name] = quantity

    return shopping_list

clothes_shop_list = add_item("Shirt", 3)
electronics_store_list = add_item("USB cable", 1)
show_list(clothes_shop_list)
show_list(electronics_store_list)

You can check whether a dictionary has been passed as an argument using the if statement. You should not rely on the falsy nature of None but instead explicitly check that the argument is None. Relying on the fact that None will be treated as a false value can cause problems if another argument that is falsy is passed.

Now when you run your script again, you’ll get the correct output since a new dictionary is created each time you use the function with the default value for shopping_list:

$ python optional_params.py

3x Shirt

1x USB cable

You should always avoid using a mutable data type as a default value when defining a function with optional parameters.

Using args and kwargs

There are two other types of Python optional arguments you’ll need to know about. In the earlier sections of this tutorial, you’ve learned how to create a function with an optional argument. If you need more optional arguments, you can create more parameters with default values when defining the function.

However, it is possible to define a function that accepts any number of optional arguments. You can even define functions that accept any number of keyword arguments. Keyword arguments are arguments that have a keyword and a value associated with them, as you’ll learn in the coming sections.

To define functions with a variable number of input arguments and keywords, you’ll need to learn about args and kwargs. In this tutorial, we’ll look at the most important points you need to know about these Python optional arguments. You can explore args and kwargs further if you want to learn more.

Functions Accepting Any Number of Arguments

Before defining a function that accepts any number of arguments, you’ll need to be familiar with the unpacking operator. You can start with a list such as the following one:

>>>
>>> some_items = ["Coffee", "Tea", "Cake", "Bread"]

The variable some_items points to a list, and the list, in turn, has four items within it. If you use some_items as an argument to print(), then you’re passing one variable to print():

>>>
>>> print(some_items)
['Coffee', 'Tea', 'Cake', 'Bread']

print() displays the list, as you would expect. However, if you had to use *some_items within the parentheses of print(), you’ll get a different outcome:

>>>
>>> print(*some_items)
Coffee Tea Cake Bread

This time, print() displays the four separate strings rather than the list. This is equivalent to writing the following:

>>>
>>> print("Coffee", "Tea", "Cake", "Bread")
Coffee Tea Cake Bread

When the asterisk or star symbol (*) is used immediately before a sequence, such as some_items, it unpacks the sequence into its individual components. When a sequence such as a list is unpacked, its items are extracted and treated as individual objects.

You may have noticed that print() can take any number of arguments. You’ve used it with one input argument and with four input arguments in the examples above. You can also use print() with empty parentheses, and it will print a blank line.

You’re now ready to define your own functions that accept a variable number of input arguments. For the time being, you can simplify add_items() to accept only the names of the items you want in the shopping list. You’ll set the quantity to 1 for each item. You’ll then get back to including the quantities as part of the input arguments in the next section.

The function signature that includes the variable number of input arguments using args looks like this:

def add_items(shopping_list, *args):

You’ll often see function signatures that use the name args to represent this type of optional argument. However, this is just a parameter name. There is nothing special about the name args. It is the preceding * that gives this parameter its particular properties, which you’ll read about below. Often, it’s better to use a parameter name that suits your needs best to make the code more readable, as in the example below:

# optional_params.py

shopping_list = {}

def show_list(shopping_list, include_quantities=True):
    print()
    for item_name, quantity in shopping_list.items():
        if include_quantities:
            print(f"{quantity}x {item_name}")
        else:
            print(item_name)

def add_items(shopping_list, *item_names):
    for item_name in item_names:
        shopping_list[item_name] = 1

    return shopping_list

shopping_list = add_items(shopping_list, "Coffee", "Tea", "Cake", "Bread")
show_list(shopping_list)

The first argument when calling add_items() is a required argument. Following the first argument, the function can accept any number of additional arguments. In this case, you’ve added four additional arguments when calling the function. Here’s the output of the above code:

$ python optional_params.py

1x Coffee
1x Tea
1x Cake
1x Bread

You can understand what’s happening with the item_names parameter by looking at a simplified example:

>>>
>>> def add_items_demo(*item_names):
...     print(type(item_names))
...     print(item_names)
...
>>> add_items_demo("Coffee", "Tea", "Cake", "Bread")
<class 'tuple'>
('Coffee', 'Tea', 'Cake', 'Bread')

When you display the data type, you can see that item_names is a tuple. Therefore, all the additional arguments are assigned as items in the tuple item_names. You can then use this tuple within the function definition as you did in the main definition of add_items() above, in which you’re iterating through the tuple item_names using a for loop.

This is not the same as passing a tuple as an argument in the function call. Using *args allows you to use the function more flexibly as you can add as many arguments as you wish without the need to place them in a tuple in the function call.

If you don’t add any additional arguments when you call the function, then the tuple will be empty:

>>>
>>> add_items_demo()
<class 'tuple'>
()

When you add args to a function definition, you’ll usually add them after all the required and optional parameters. You can have keyword-only arguments that follow the args, but for this tutorial, you can assume that args will usually be added after all other arguments, except for kwargs, which you’ll learn about in the following section.

Functions Accepting Any Number of Keyword Arguments

When you define a function with parameters, you have a choice of calling the function using either non-keyword arguments or keyword arguments:

>>>
>>> def test_arguments(a, b):
...     print(a)
...     print(b)
...
>>> test_arguments("first argument", "second argument")
first argument
second argument
>>> test_arguments(a="first argument", b="second argument")
first argument
second argument

In the first function call, the arguments are passed by position, whereas in the second one they’re passed by keyword. If you use keyword arguments, you no longer need to input arguments in the order they are defined:

>>>
>>> test_arguments(b="second argument", a="first argument")
first argument
second argument

You can change this default behavior by declaring positional-only arguments or keyword-only arguments.

When defining a function, you can include any number of optional keyword arguments to be included using kwargs, which stands for keyword arguments. The function signature looks like this:

def add_items(shopping_list, **kwargs):

The parameter name kwargs is preceded by two asterisks (**). The double star or asterisk operates similarly to the single asterisk you used earlier to unpack items from a sequence. The double star is used to unpack items from a mapping. A mapping is a data type that has paired values as items, such as a dictionary.

The parameter name kwargs is often used in function definitions, but the parameter can have any other name as long as it’s preceded by the ** operator. You can now rewrite add_items() so that it accepts any number of keyword arguments:

# optional_params.py

shopping_list = {}

def show_list(shopping_list, include_quantities=True):
    print()
    for item_name, quantity in shopping_list.items():
        if include_quantities:
            print(f"{quantity}x {item_name}")
        else:
            print(item_name)

def add_items(shopping_list, **things_to_buy):
    for item_name, quantity in things_to_buy.items():
        shopping_list[item_name] = quantity

    return shopping_list

shopping_list = add_items(shopping_list, coffee=1, tea=2, cake=1, bread=3)
show_list(shopping_list)

The output from this code displays the items in the dictionary shopping_list, showing all four things you wish to buy and their respective quantities. You included this information as keyword arguments when you called the function:

$ python optional_params.py

1x coffee
2x tea
1x cake
3x bread

Earlier, you learned that args is a tuple, and the optional non-keyword arguments used in the function call are stored as items in the tuple. The optional keyword arguments are stored in a dictionary, and the keyword arguments are stored as key-value pairs in this dictionary:

>>>
>>> def add_items_demo(**things_to_buy):
...     print(type(things_to_buy))
...     print(things_to_buy)
...
>>> add_items_demo(coffee=1, tea=2, cake=1, bread=3)
<class 'dict'>
{'coffee': 1, 'tea': 2, 'cake': 1, 'bread': 3}

To learn more about args and kwargs, you can read Python args and kwargs: Demystified, and you’ll find more detail about keyword and non-keyword arguments in functions and the order in which arguments can be used in Defining Your Own Python Function.

Conclusion

Defining your own function to create a self-contained subroutine is one of the key building blocks when writing code. The most useful and powerful functions are those that perform one clear task and that you can use in a flexible manner. Using optional arguments is a key technique to achieve this.

In this tutorial, you’ve learned:

  • What the difference is between parameters and arguments
  • How to define functions with optional arguments and default parameter values
  • How to define functions using args and kwargs
  • How to deal with error messages about optional arguments

A good understanding of optional arguments will also help you use functions in the standard library and in other third-party modules. Displaying the documentation for these functions will show you the function signature from which you’ll be able to identify which arguments are required, which ones are optional, and which ones are args or kwargs.

However, the main skill you’ve learned in this tutorial is to define your own functions. You can now start writing functions with required and optional parameters and with a variable number of non-keyword and keyword arguments. Mastering these skills will help you take your Python coding to the next level.

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Stephen Gruppetta

Stephen Gruppetta Stephen Gruppetta

Stephen worked as a research physicist in the past, developing imaging systems to detect eye disease. He now teaches coding in Python to kids and adults. And he's almost finished writing his first Python coding book for beginners

» More about Stephen

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Join us and get access to hundreds of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Join us and get access to hundreds of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Real Python Comment Policy: The most useful comments are those written with the goal of learning from or helping out other readers—after reading the whole article and all the earlier comments. Complaints and insults generally won’t make the cut here.

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Keep Learning

Related Tutorial Categories: basics python