Functions in Python

Learning objectives

Every algorithm for solving some computational problem can be represented using a function that takes a collection of parameters as input and that processes these parameters to return some output solving the problem. For example, consider the pseudocode function EuclidGCD() reproduced below from the core text, which takes as input two integer parameters a and b, and returns their greatest common divisor.

EuclidGCD(a,b)
    while a ≠ b
        if a > b
            a ← a − b
        else
            b ← b − a
    return a 

A primary purpose of this course is to reprogram our brains to think about functions as the building blocks that we use to solve computational problems. Let us therefore learn a little about function syntax in Python.

Code along summary

Setup

Create a folder called functions in your python/src directory, and create a text file called main.py in the python/src/functions folder. We will edit main.py in this lesson.

As in the previous code along, we provide some starter code for  main.py below.

def main():
    print("Functions in Python.")


if __name__ == '__main__':
    main()

Our first function: summing two integers

As we declare variables, we declare functions. If we have a collection parameters of variables used as input to some function named function_name(), then we can declare this function using the syntax def function_name(parameters):. In this notation, each variable from parameters is specified to the function.

This notation might seem familiar, because we have already used it when establishing the code that we want to run in def main(). In fact, main(), the component of our code that Python will execute, is a special function that takes no inputs and returns no outputs.

Our first Python function other than main() is called sum_two_ints() and is shown below. This function takes two variables of type int as input and returns their sum. (This function is provided for the sake of simplicity; we can simply use the + operator to add numeric variables.)

def sum_two_ints(a, b):
    return a + b
Note: In Python, function names are like variable names and conventionally use snake_case, where words are lowercase and separated by underscores. In any programming language, it is good practice to start a function with an action verb (in this case, “sum”) and to make functions descriptive so that they are easy for an observer to understand.

Calling functions

Let’s return to main() and show how that we can use our function now that we have written it. Let’s call the sum_two_ints() function using a single variable x as shown below. We will use a variable n to store the output of this function, and then print the value of n to the console.

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

def main():
    x = 3
    n = sum_two_ints(x)
    print(n)

if __name__ == '__main__':
    main()
STOP: Let’s run this code. Open a command line terminal and navigate into the folder containing your code by executing the command cd python/src/functions. Then, run the code by executing the command python3 main.py (macOS/Linux) or python main.py (Windows). What happens?
Click Run 👇 to try it!

When we run this code, Python provides an error with the feedback TypeError: sum_two_ints() missing 1 required positional argument: 'b'. When we pass inputs to functions, we must ensure that we pass in the correct number of variables. In this case, sum_two_ints() demands two parameters, and Python therefore throws an error when we attempt to call it with only one input.

Let’s fix the issue by adding an additional parameter. We could declare another variable to pass as input to sum_two_ints(), but we will instead pass the constant 4 as the second parameter to show that if a function demands an integer input, we can pass in either an integer variable or an integer “literal” like 4 or -170.

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

def main():
    print("Functions in Python.")
    x = 3
    print("3 + 4 is", sum_two_ints(x, 4))

if __name__ == '__main__':
    main()
Click Run 👇 to try it!

The order of functions does not matter, unless it does

You may be curious why we placed sum_two_ints() before main() in the above code. In a previous code along, we noted that we can place as many lines of whitespace into your code as you like without any issue. For the most part, we can place functions into a Python file in any order that we like. Even if a function calls another function as a subroutine, the subroutine can occur before or after the function calling it.

For example, we could place sum_two_ints() between def main() and the call to run main(), as shown below.

Click Run 👇 to try it!

However, when we place sum_two_ints() at the end of our code, something else happens.

Click Run 👇 to try it!

The NameError that we obtain when running the code above is shown below. Note the usage of ^ symbols to indicate the function that is problematic on the third line of main().

Error: Traceback (most recent call last):
  File "/home/runner/workspace/temp/main_lYDnGKH_dpuFtrAopgbgC.py", line 6, in <module>
    main()
  File "/home/runner/workspace/temp/main_lYDnGKH_dpuFtrAopgbgC.py", line 3, in main
    print("3 + 4 is", sum_two_ints(3, 4))
                      ^^^^^^^^^^^^
NameError: name 'sum_two_ints' is not defined 

Python is telling us that the sum_two_ints() function is undefined because the following code executes what is contained in main().

if __name__ == '__main__':
    main()

This example illustrates a critical rule of Python: function definitions may appear in any order, but any functions used during execution must have been defined. The above code executes main(), and although main() has already been defined, it calls sum_two_ints() as a subroutine, which has not yet been defined.

As a general practice, we will define functions in any order that we like, but we will always make sure to save the execution of main() until the final lines of our program.

Every Python function should have a docstring

We will now show how to improve our first function by adding two commonly used stylistic elements to it. First, we provide a multiple line comment called a docstring (short for “documentation string”) indicating what the function does. Each function we write should have a docstring with the following properties:

  1. The first and last lines of the docstring start with three double quotation marks (""").
  2. The second line of the docstring explains, in complete sentences, what the function does.
  3. Remaining lines of the docstring explain the inputs and outputs of the function.
def sum_two_ints(a, b):
    """
    Add two integers and return their sum.

    Parameters:
    - a (int): an input integer
    - b (int): an input integer

    Returns:
    int: a + b
    """

    return a + b

Docstrings will make you a stronger programmer because they ensure that you think about functions in terms of inputs and outputs. However, they are also very useful as a documentation practice, as an observer can access the docstring in a variety of ways to know more about the function. For example, we can obtain the docstring of a function by passing the name of that function as input to the built-in Python function help(), as shown below. The help() function offers an early example of another phenomenon: functions used as parameters to other functions!

def sum_two_ints(a, b):
    """
    Add two integers and return their sum.

    Parameters:
    - a (int): an input integer
    - b (int): an input integer

    Returns:
    int: a + b
    """
    
    return a + b

def main():
    print("Functions in Python.")
    print("3 + 4 is", sum_two_ints(3, 4))
    help(sum_two_ints)

if __name__ == '__main__':
    main()
Click Run 👇 to try it!

Type hints will make you a stronger Python programmer

Many programming languages are very strict about types, demanding for example that the user specify the types of any parameters within the function signature, which is the first line of the function. As we have seen, Python does not have the requirement of specifying types, but it does support type hints, in which we indicate suggested types of any parameters as well as any values that the function returns.

In general, we provide a type hint for a parameter by specifying the name of the parameter, followed by a colon, followed by its suggested type. We provide a type hint for the return value by adding, after the closing parentheses enclosing any parameters, an arrow -> followed by the types of any values returned. With this in mind, we can update the function signature of sum_two_ints() as follows:

def sum_two_ints(a: int, b: int) -> int:

We now will show the complete sum_two_ints() function below. We will use type hints throughout our work with Python because we should always know the types of any parameters.

def sum_two_ints(a: int, b: int) -> int:
    """
    Add two integers and return their sum.

    Parameters:
    - a (int): an input integer
    - b (int): an input integer

    Returns:
    int: a + b
    """
    
    return a + b
STOP: What happens if we call sum_two_ints() using two non-integers (e.g., sum_two_ints(-2.1, 4.78))?
Click Run 👇 to try it!

Our second function, and a brief word about tuples

Just as functions can take more than one input variable, they can return more than one output variable as well. For example, consider the following function double_and_duplicate() that takes a float variable x as input and returns two copies of 2 * x. Whenever we want to store multiple variables together at once, we use a tuple; we will say more about tuples soon. Below, tuple[float, float] in the return type hint indicates that we will be returning a tuple containing two floats.i

def double_and_duplicate(x: float) -> tuple[float, float]:
    """
    Double the input variable and return two copies of it.

    Parameters:
    - x (float): an input float

    Returns:
    tuple[float, float]: Two copies of 2 * x.
    """

    return 2 * x, 2 * x
Click Run 👇 to try it!

Although we typically think of functions as taking some input variable(s) and producing output value(s) solving some problem, functions do not need to take inputs or return anything, like main(). For example, the following function takes an action by printing Hi to the console, but it does not return a value. (Technically, it returns None, but we will save that discussion for another day.)

def print_hi():
    """
    Takes no input and simply prints "Hi" to the console.
    """

    print("Hi")
Click Run 👇 to try it!
Note: Beginner programmers often conflate the ideas of a function returning the value of a variable and a function printing the value of a variable. A variable returned by a function can be printed, but it can also be used as input to another function, as we saw in the main text with functions used as subroutines. In print_hi(), a statement is printed to the console, but the function does not return anything.

Python uses pass by value for input parameters

Consider the function below, which adds one to the value of an input integer variable and then returns the variable.

def add_one(k: int) -> int:
    """
    Add one to the input variable k and return the result.

    Parameters:
    - k (int): an input integer

    Returns:
    int: k + 1
    """

    k = k + 1
    return k

Consider the following def main().

def main():
    m = 17
    print(add_one(m))

When we run our code, the final thing that we will see printed is 18, as we might expect. However, let’s see what happens when we also print m at the end of main().

def main():
    m = 17
    print(add_one(m))
    print(m)

You might imagine that the last line of this code will also print the value 18. But when we run our code, we see that it instead prints 17! You can verify this by running the code below.

Click Run 👇 to try it!

When we call add_one(m), a copy of m (called k) is created, and it is this copy that the function edits and then destroys at the end of the function body. When we run the above code, we see that k indeed receives the value of 18, but that m remains equal to 17.

Even if we rename the input parameter of add_one() from k to m, as shown in the below code complete with comments, the same end behavior occurs.

Click Run 👇 to try it!

This example is part of a larger principle called pass by value (see figure below), which Python uses for many variable types, including strings, symbols, integers, and decimal numbers. Python does not use pass by value for all data types; we will see soon in the class that for some types, Python instead uses pass by reference, in which variables input to a function are not copied and any changes to these variables inside the function can affect the variables outside the function as well.

Figure: An illustration of pass by reference and pass by value using cups of coffee. In pass by reference, variables passed as input to a function can be changed by the function. In pass by value, which is used by Python for the types that we will work with in this chapter, input variables are copied, and any changes to these variables affect only the copies. Courtesy: penjee.com.
Note: Technically, Python passes parameters by object reference, a concept that we will explain later in the course when we discuss objects and memory management in order to provide a clearer picture.

Looking ahead

Now that we understand the basics of functions, we are now ready to continue exploring how Python implements the basics of control flow. In the next lesson, we will move on to discuss conditionals like if and else, which will prepare us to implement some ancient Greek algorithms.

Check your work from the code along

Throughout this course, we will give you the opportunity to check your work via autograded code challenges. For example, after each code along, we provide short code challenges based on the functions covered in the code along so that you can ensure that the functions that you wrote are correct.

At the end of each chapter, we also include a collection of autograded exercises. All of these autograded assessments give you points, and exercises count for more than code along checks. If you earn enough points over all these challenges, you will earn a certificate of completion!

Our code assessments are hosted on a platform called Cogniterra. To get started, create an account at cogniterra.org and join our course at https://cogniterra.org/course/63. You can then log in to Cogniterra using the window below, and your progress will be automatically tracked across this course.

The following lesson allows you to check your sum_two_ints() and double_and_duplicate() functions. It can also be found using a direct link. Beneath the window below, we provide a link to the next lesson.

powered by Advanced iFrame

Scroll to Top