Special Arguments

So far, we have seen functions that take a fixed number of arguments. This is also known as positional arguments. When we call a function, we pass the arguments in the order they are defined in the function signature, separated by commas. Python will match the arguments to the parameters based on their position.

However, Python also supports keyword arguments. Keyword arguments are passed as key-value pairs, where the key is the parameter name. This allows us to pass arguments in any order, and to skip arguments with default values. Default arguments are another nice feature of Python functions, provided in the function signature. If an argument is not passed, the default argument value is used instead.

Finally, Python has two special kinds of arguments: *args and **kwargs. Python has a syntax for unpacking lists and dictionaries into arguments. We’ll talk more about these in a minute, but first let’s look at keyword arguments.

Parameters Are Not Arguments

def greet(name: str, greeting: str) -> str:
    return f"{greeting}, {name}!"

name = "Ada"
greeting = "Hello"

# Both of these are valid
print(greet(name, greeting))  # Output: "Hello, Ada!"
print(greet(greeting, name))  # Output: "Ada, Hello!"

A common novice mistake is to confuse parameters with arguments. Parameters are the names in the function signature, that are used to refer to the values passed in. Parameters are variables, and are local to the function.

Arguments are the values passed in when the function is called. Positional arguments are matched to parameters based on their order.

If a variable is used as an argument, the value of the variable is passed in. The name of the variable is not passed in, only the value. In fact, the name of the argument variable has no relationship to the parameter name, even if they coincidentally match sometimes. It’s reasonable that a function might have a parameter named name and an argument named name, but it’s not required. In this example, we can see that the order of the arguments matters, not the names.

Keyword Arguments

def greet(name: str, greeting: str) -> str:
    return f"{greeting}, {name}!"

name = "Ada"
greeting = "Hello"

# Both of these are valid
print(greet(name=name, greeting=greeting))  # Output: "Hello, Ada!"
print(greet(greeting=greeting, name=name))  # Output: "Hello, Ada!"

Keyword arguments are passed as key-value pairs, almost as if they were assignment statements. This is good mental model: when you call a function with keyword arguments, you are assigning values to the parameters explicitly. Since the key is the parameter name, the order of the arguments does not matter. This is especially useful when a function has many parameters, and you only want to pass a few of them. For that, we need to use default arguments.

Default Arguments

def rectangle_area(length=1, width=1):
    return length * width

print(rectangle_area())  # 1
print(rectangle_area(length=5))  # 5
print(rectangle_area(width=4, length=3))  # 12

Default arguments are provided in the function signature. If an argument is not passed, the default argument value is used instead. This is useful when you have a common case that you want to handle without requiring the caller to provide a value. In this example, the default rectangle is a 1x1 square. If you want a different size, you can pass the length and width as arguments.

Default Arguments Are Evaluated Once

def add_to_list(value, lst=[]):
    lst.append(value)
    return lst

print(add_to_list(1))  # [1]
print(add_to_list(2))  # [1, 2]
print(add_to_list(3))  # [1, 2, 3]

Default arguments are evaluated once, when the function is defined. This can lead to unexpected behavior if the default argument is mutable. In this example, the default argument is a list. The list is created once, when the function is defined. Each time the function is called, the same list is used. This is why the list grows each time the function is called. You should avoid ever using a mutable default argument like a list or a dictionary.

None as an Argument

def add_to_list(value, lst=None):
    if lst is None:
        lst = []
    lst.append(value)
    return lst

print(add_to_list(1))  # [1]
print(add_to_list(2, [4, 5]))  # [4, 5, 2]
print(add_to_list(3))  # [3]

To avoid this, use None as the default value, and create the mutable object inside the function.

*args: Variadic Args

def add(*numbers):
    total = 0
    for num in numbers:
        total += num
    return total

print(add(1, 2, 3))  # 6
print(add(4, 5, 6, 7))  # 22

Python has a special syntax for functions that take a variable number of arguments. By preceding a parameter with an asterisk, the function can take any number of arguments; the name of the parameter is arbitrary, but args is a common choice. This is called a variadic parameter, or “varargs”. Note that you can only have one variadic parameter in a function. The arguments are collected into a tuple (essentially a list), which can be iterated over. This is useful when you want to pass a variable number of arguments to a function. The print function is a good example of a variadic function, since it can take any number of parameters.

In the example shown here, the function add takes any number of arguments and returns the sum of all the arguments. The *numbers parameter collects all the arguments into a tuple called numbers. The function then iterates over the tuple, adding each number to the total.

**kwargs: Keyword Args

def add_grades(**grades):
    total = 0
    count = 0
    students = ""
    for student, grade in grades.items():
        total += grade
        count += 1
        students += student + " "
    return "The students are: " + students + "and the average grade is: " + str(total / count)

print(add_grades(Alice=90, Bob=85, Charlie=80))
# The students are: Alice Bob Charlie and the average grade is: 85.0

print(add_grades(Alice=90, Bob=85, Charlie=80, David=95))
# The students are: Alice Bob Charlie David and the average grade is: 87.5

Python also has a special syntax for functions that take a variable number of keyword arguments. By preceding a parameter with two asterisks, the function can take any number of keyword arguments; the name of the parameter is arbitrary, but kwargs is a common choice. This is called a keyword argument parameter. The arguments are collected into a dictionary, which can be iterated over. This is useful when you want to pass a variable number of keyword arguments to a function.

In the example shown here, the function add_grades takes any number of keyword arguments and returns the average grade of the students. The **grades parameter collects all the keyword arguments into a dictionary called grades. The function then iterates over the dictionary, adding each grade to the total and counting the number of students. Finally, the function calculates the average grade and returns a string with the list of students and the average grade.

Unpacking Lists

def add(a, b):
    return a + b

numbers = [1, 2]
print(add(*numbers))  # 3
print(add(*[10, 3]))  # 13

We have one last bit of syntax to go over: argument unpacking. Python allows you to “unpack” a list (or other sequence) into individual arguments. This is useful when you have a list of values that you want to pass as arguments to a function. You can think of it as the inverse of the *args parameter syntax. In this example, the list [1, 2] is unpacked into the arguments 1 and 2, which are then passed to the add function.

Unpacking Dictionaries

def greet(name: str, greeting: str) -> str:
    return f"{greeting}, {name}!"

person = {"name": "Ada", "greeting": "Hello"}
print(greet(**person))  # Output: "Hello, Ada!"

You can also unpack a dictionary into keyword arguments. This is useful when you have a dictionary of values that you want to pass as keyword arguments to a function. In this example, the dictionary person is unpacked into the keyword arguments name="Ada" and greeting="Hello", which are then passed to the greet function.

Variable Arguments vs Unpacking

It is easy to confuse variable arguments (*args and **kwargs) with unpacking (* and **), since they use complementary syntax. However, they are different concepts. *args and **kwargs are special parameters that allow a function to take a variable number of arguments. * and ** unpacking is a special syntax that allows you to pass a variable number of arguments to a function via a function call. Notably, the former are special kinds of parameters and are defined in the function signature. The latter are special kinds of arguments and are used in the function call.

Summary