2

Using Python Optional Arguments When Defining Functions

 3 years ago
source link: https://realpython.com/python-optional-arguments/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Using Python Optional Arguments When Defining Functions – Real Python

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:

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.

Error Messages Related to Input Arguments

One of the most common error messages you’ll encounter is when you call a function that has required arguments, but you don’t pass the arguments in the function call:

# optional_params.py

# ...

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

add_item()

Here, you call add_item() without passing the required arguments item_name and quantity. You’ll get a TypeError whenever a required argument is missing:

$ python optional_params.py
  File "optional_params.py", line 15
    add_item()
TypeError: add_item() missing 2 required positional arguments: 'item_name' and 'quantity'

The error message is a helpful one in this case. Error messages are not always as helpful as this one. However, missing required parameters are not the only error message you’ll encounter as you learn to define functions with required and optional parameters.

When none of the parameters in a function definition has default values, you can order the parameters in any way you wish. The same applies when all the parameters have default values. However, when you have some parameters with default values and others without, the order in which you define the parameters is important.

You can try to swap the order of the parameters with and without default values in the definition of add_item():

# optional_params.py

# ...

def add_item(shopping_list=None, item_name, quantity):
    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

The error message you’ll get when you run this code explains the rule fairly clearly:

$ python optional_params.py
  File "optional_params.py", line 5
    def add_item(shopping_list=None, item_name, quantity):
                                                ^
SyntaxError: non-default argument follows default argument

The parameters with no default value must always come before those that have a default value. In the above example, item_name and quantity must always be assigned a value as an argument. Placing parameters with default values first would make the function call ambiguous. The first two required arguments can then be followed by an optional third argument.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK