Using Python Optional Arguments When Defining Functions – Real Python


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.

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.

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:

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.

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.