Chapter 6

Functions

Subsections of Functions

Introduction

YouTube Video

Resources

As our Python programs continue to get larger and more complex, we may notice that we are reusing certain pieces of code over and over again. A great example is the loop structure to repeatedly get input from the user until a valid input is received.

x = float(input("Enter a percentage as a decimal number from 0 to 1: "))
while(x < 0 or x > 1):
    print("Invalid Input!")
    x = float(input("Enter a percentage as a decimal number from 0 to 1: "))

This is a very useful piece of code, and we can easily copy and paste it wherever we need it in our programs. Unfortunately, this means that anytime we want to update or change that code, we have to manually change every location where it is used in our programs. In a large program, this can be very tricky to do correctly without causing other errors in the code.

Thankfully, Python allows us to create small, repeatable blocks of code called functions that we can write once, and then use anywhere we’d like within our programs. We’ve already used many different functions in our programs, from the print() function used to display output to the user and the input() function used to get input from the user, all the way to the random.randint() function we’ve used to generate random numbers in our code.

Functions also allow us to break our larger programs up into smaller, easier to understand steps. This makes our code much easier to read, test, and debug. Learning how to build programs in terms of individual functions instead of just simple steps is a big part of becoming a more experienced programmer. So, in this lab, we’re going to learn all about how to build, use, and test our own functions in Python.

Subsections of Introduction

Function Basics

YouTube Video

Resources

Creating a Function

A simple function in Python uses the following structure:

def function_name():
    <block of statements>

Let’s break this structure down to see how it works:

  1. First, a function definition in Python begins with the word def, which is short for define. In effect, we are stating that we are defining a new function using the special def keyword.
  2. Next, we include the name of the function. Function names in Python follow the same rules as variable names:
  3. A function name must begin with a letter or underscore _. Function names beginning with an underscore _ have special meaning, so we won’t use those right now.
  4. A function name may only include letters, numbers, and underscores _.
  5. After the function name, we see a set of parentheses (). This is where we can add the parameters for the function. We’ll see how to add function parameters later in this lab.
  6. Then, we end the first line of a function definition in Python with a colon :. This tells us that the block of statements below the function definition is what should be executed when we run the function. We’ve already seen colons in both conditional statements and loops, and here it is used in a similar way.
  7. Finally, we have the block of statements that are part of the function itself. This block of statements must be indented one level to indicate that it is part of the function and not something else. When we reach the end of the function, we cease to indent the code, which tells Python that we’ve ended the function and are starting something else. So, notice that there is no closing curly brace } or any other marker to indicate the end of the function - only the indentation tells us where the function’s code ends. Again, this is very similar to how conditional statements and loops are structured in Python.

Function Example

Let’s look at an actual function in Python. In this case, we’ll create a simple hello_world function in Python:

def hello_world():
    print("Hello World")

Really, this structure is exactly like we’d expect it to be, based on the structures we’ve learned so far in Python. We start with the def keyword, then the name of the function. After that, we have parentheses () where the parameters would go, and then a colon : to show the start of the block of statements. Inside of the function, we indent the block of statements that make up the code to be run when this function is run - in this case we just print the string "Hello World" to the terminal. That’s really all there is to it!

Calling a Function

Likewise, calling a function in Python is exactly the same as what we’ve seen with the print() function - we simply state the name of the function, followed by a set of parentheses where we would place the arguments if the function required any parameters:

hello_world()

So, a complete Python program that includes the hello_world function and calls that function would look like this:

def hello_world():
    print("Hello World")


hello_world()

Try placing this code in a Python file and see if you can run it. It should work!

Notice that the function call for hello_world is not indented, and we included a couple of blank lines after the end of the definition of that function before the next piece of code. We do this for two reasons:

  1. We do not indent code that is not part of the block of statements within a function. Since the function call for hello_world should not be part of the definition for the function, we don’t want to indent it.
  2. In Python, the convention is to include two blank lines at the end of a function definition. So, that is why we included two blank lines in the code. These blank lines are not required, but it is good practice to include them.

Code Tracing a Function Call

Finally, let’s briefly go through an example of tracing a program that includes a couple of functions and function calls. Here’s a quick example we can use:

def foo():
    print("eye")


def bar():
    print("to")


foo()
bar()
foo()

To trace this example, copy-paste the code into Python Tutor, or click this Python Tutor link.

When the program first loads, we should see this setup:

Python Tutor 1 Python Tutor 1

Again, this is very familiar to us based on what we’ve seen so far. So, let’s click the Next > button to step through the first part of the code:

Python Tutor 2 Python Tutor 2

As we can see, the first thing that Python sees is the definition for the foo function. Instead of keeping a separate list of functions, Python Tutor simply adds that function to the global frame just like any variable. The function itself is added to the Objects list, and then there is an arrow, connecting the variable in the global frame to the function in the objects list. In fact, this is exactly how Python handles functions - they are just treated like variables that are pointers to the function objects themselves! So, when we click Next > again, we’ll see it do the same to the bar function as well:

Python Tutor 3 Python Tutor 3

At this point, we are ready to execute the first function call, which will run the foo function. So, when we click the Next > button, we’ll see the arrow jump to the start of that function:

Python Tutor 4 Python Tutor 4

Here, we see Python tutor do something a bit different than in our own code traces - instead of keeping all of the variables in a single box, it creates a new frame to keep track of the variables that are created in the foo function. When we look an example of a function with parameters, we’ll see where this becomes very useful. For now, we can just ignore it and move on by clicking Next > once again:

Python Tutor 5 Python Tutor 5

At this point, the function is ready to print the string value "eye" to the terminal. So, if we click Next > once again, we should see that reflected in the output of Python Tutor:

Python Tutor 6 Python Tutor 6

At this point, we’ve reached the end of the foo function. So, Python Tutor will remove the foo frame from the list of frames and it will jump back down to where the function was called from when we click Next >:

Python Tutor 7 Python Tutor 7

Here, we are back to the main piece of our code, and we’re ready to call the bar function. So, we can click Next > once again:

Python Tutor 8 Python Tutor 8

This will jump up to the start of the bar function and create a new frame for storing any variables created by bar. We can click Next > again to enter the function:

Python Tutor 9 Python Tutor 9

And then click Next > again to print the string value "to" to the output:

Python Tutor 10 Python Tutor 10

Once again, we are at the end of a function, so when we click Next > once more:

Python Tutor 11 Python Tutor 11

We’ll move back to the main piece of the code, removing the frame for bar. On this line, we see yet another function call, this time to the foo function. Hopefully by this point we can predict what is going to happen, so we can click Next > a few more times to jump to the foo function and execute it. When it is done, we’ll see this output:

Python Tutor 15 Python Tutor 15

There we go! We’ve successfully traced the execution of a Python program that contains multiple function calls. The full process is shown in this animation:

Python Tutor Animation Python Tutor Animation

Using tools like Python Tutor to trace through code is a great way to make sure we understand exactly what our computer is doing when it is running our code.

Subsections of Function Basics

Main Function

YouTube Video

Resources

Let’s review a couple other concepts related to functions in Python.

Main Function

We can also create a main function in Python. The use of a main function in Python is not required at all - Python is designed as a scripting language, meaning we can write code directly in a Python file without using any functions at all. However, it is generally considered good practice to make sure all code is part of a function, and then we can include a main function as the starting point for any program.

So, we can update the example we saw previously to include a main function by simply placing the three function calls in a new function called main, and then including a call to the main function at the bottom of the program:

def foo():
    print("eye")


def bar():
    print("to")


def main():
    foo()
    bar()
    foo()


main()

From here on out in this course, we’ll follow this convention in our complete Python programs. Specifically:

  1. All programs must contain a function named main as the starting point of the program.
  2. All code in a program must be contained within a function, with the exception of a single call to the main function at the bottom of the program.

This will make our Python programs easy to follow, and it will help us later on down the road if we choose to learn another language such as Java or C# that requires a main function.

Subsections of Main Function

Parameters & Arguments

YouTube Video

Resources

Function Parameters

Functions in Python can also require parameters. To include a parameter in a function, we simply have to include the name of the parameter in the parentheses () at the end of the function definition. Multiple parameters should be separated by commas ,. For example, we can update our hello_world function to include a couple of parameters:

def hello_world(first_name, last_name):
    print("Hello ", end="")
    print(first_name, end=" ")
    print(last_name)

Here, we are defining two parameters, named first_name and last_name, that are used as part of the function. Those parameters can be treated just like any other variable within the function.

Calling a Function with Arguments

Once we have a function that requires parameters, we can call it by including arguments in the parentheses () that are part of the function call. For example, we can call the hello_world function above and provide two arguments like this:

hello_world("Willie", "Wildcat")

When we run this code, it will place the string value "Willie" in the parameter variable first_name, and the string value "Wildcat" will be placed in the parameter value last_name within the frame for the hello_world function. When the code in hello_world is executed, we should see the following output:

Hello Willie Wildcat

Code Tracing a Function with Arguments

To see how this works, let’s work through a full example. Here’s a more complex Python program that includes parameters, arguments, and some other variable expressions:

def flip(first, last):
    temp = first
    first = last
    last = temp
    print(first)
    print(last)


def main():
    first = "Willie"
    last = "Wildcat"
    flip(first, last)
    print(first)
    print(last)


main()

Once again, before reading the full analysis below, take a minute to read through this code and see if you can guess what it does. It can be a bit tricky if you don’t read it carefully and think about what we’ve learned so far.

To trace this example, copy this code to Python Tutor, or click this Python Tutor link.

When we begin, our code trace will look like this example:

Python Tutor 1 Python Tutor 1

As we expect, the first thing that Python will do is scan through the code and record any functions it finds in the global frame. So, after pressing the Next > button a couple of times, we should reach this point:

Python Tutor 3 Python Tutor 3

Now we are at a function call for the main function. So, when we click the Next > button:

Python Tutor 4 Python Tutor 4

Python tutor will jump to that function’s code, and it will also create a new frame for variables that are created in the main function. The next two lines deal with creating a couple of variables, so we can click the Next > button a couple of times to execute those lines and stop when we reach the next function call:

Python Tutor 7 Python Tutor 7

Now we are ready to call the flip function. This function requires two parameters, named first and last. Notice that those parameter names are the same names as the variables that we created in the main function? This is a common practice in programming - sometimes it is simplest to use the same variable names in multiple functions, especially if they are storing the same data. However, it is important to understand that those variables are not related in any way except for the name, as we’ll see in this example. When we click the Next > button to jump to the start of the flip function, we should see the following in our Python tutor trace:

Python Tutor 8 Python Tutor 8

It has now created a frame for the flip function, and copied the values of the two arguments into the appropriate parameter variables. Since we listed the arguments first and last in that order in the function call to flip, a copy of the values from those two variables in main will be stored in the same two variable names in flip. It’s an easy way to make sure the arguments are in the correct order!

The first three lines in the flip function will swap the values in the first and last parameter variables. So, after we’ve executed those three lines, we should now see this setup in our trace:

Python Tutor 12 Python Tutor 12

Notice that the values in first and last inside of the flip frame have changed, but the values in the same variables in the main frame have not changed! This is why it is very useful to keep track of variables in frames within a code trace - we can easily tell which variables go with which function, even if they have the same names. So, the next two lines of code in the flip function will simply print out the contents of the first and last parameter variables:

Python Tutor 14 Python Tutor 14

At this point, we’ve reached the end of the flip function, so when we click the Next > button again, we’ll jump back down to where we left off in the main function. At the same time, we’ll remove the flip frame from the list of frames, completely removing the first and last parameter variables used in that function:

Python Tutor 15 Python Tutor 15

Now that we are back in the main function, we can see that the values stored in the first and last variable are unchanged, just like we’d expect. This is important to understand - just because one function uses the same variable names as another function, or that a function’s parameter names match the variable names provided as arguments, they are not related and any changes in the function won’t impact the values outside of the function. So, we can finish the main function by running the last two lines, which will print the current values of first and last to the screen:

Python Tutor 17 Python Tutor 17

Finally, once the program reaches the end of the main function, it will jump back to the main function call at the bottom of the program. This will remove the main frame from the list of frames. Since there is nothing more to do, the program will end at this point:

Python Tutor 18 Python Tutor 18

The whole process can be seen in this animation:

Python Tutor Animation 3 Python Tutor Animation 3

There we go! We’ve explored how functions, parameters, and arguments all work in Python. Understanding this process now will make it much easier to write complex programs later on.

Subsections of Parameters & Arguments

Return

YouTube Video

Resources

Python functions are also capable of returning a value, In Python, we use the return keyword.

Let’s look at a quick example of a full Python program that includes a function that returns a value:

def square_sum(one, two):
    one = one * one
    two = two * two
    total = one + two
    return total


def main():
    text_one = input("Enter the first number: ")
    one = int(text_one)
    text_two = input("Enter the second number: ")
    two = int(text_two)
    total = square_sum(one, two)
    print(f"The sum of squares of {one} and {two} is {total}")


main()

To truly understand how this program works, let’s use Python Tutor to explore it step by step. Like before, copy the code into Python Tutor, or click this Python Tutor link.

At the start, our Python Tutor trace will look like this:

Python Tutor 1 Python Tutor 1

The first few steps are pretty straightforward, since Python will simply move through the code and record all of the functions it finds. Once we reach the call to the main() function at the bottom, we’ll be at this state:

Python Tutor 3 Python Tutor 3

So, we’ll enter the main() function and start executing the code it contains. The first line is an input() expression, so Python will prompt the user for input. In Python Tutor, this will open a box at the bottom where we can enter the input, as shown below:

Python Tutor 5 Python Tutor 5

Let’s assume that the user would enter 2 for this input. So, we can type that into the box and press ENTER or click submit. Python tutor will refresh the page to accept the input, and then we’ll be at this setup:

Python Tutor 6 Python Tutor 6

Next, we’ll convert the string value "2" that is currently stored in the text_one variable to an integer using the int() function and store it in the variable one. On the next line, it will ask for input once again:

Python Tutor 7 Python Tutor 7

In this case, we’ll assume the user is inputting 3, and then we’ll store it and convert it to an integer stored in the variable two:

Python Tutor 9 Python Tutor 9

At this point, we’re ready to call the square_sum() function. So, Python Tutor will find the arguments for the function call and prepare to store them in the parameter variables of the function. When we click next, we’ll see this setup in our window:

Python Tutor 10 Python Tutor 10

Recall that Python Tutor will create a new frame for the square_sum() function in the frames area and store the variables from that function there. This will become important later when we reach the end of the function. Inside of the square_sum() function, we just perform a few mathematical operations, culminating with the total variable storing the sum of the squares of the two arguments, as shown here:

Python Tutor 14 Python Tutor 14

At this point, we’ve reached the return statement at the end of the square_sum() function. When we click next on Python Tutor, we’ll see something new appear:

Python Tutor 15 Python Tutor 15

When Python reaches the end of a function, it will record the return value in the function’s frame before leaving the function. This value is what is given back to the main() function. So, when we click next again, we’ll see this state:

Python Tutor 16 Python Tutor 16

We’re back in the main() function, and now we can see that the new total variable stores the value $13$, which was the returned value from the square_sum() function. So, on this last line, we’ll see that the program will create an output statement using a template string and the string format() method, and it will display output to the user:

Python Tutor 17 Python Tutor 17

What’s also interesting to note here is the return value of the main() function. Since we didn’t include a return statement at the end of the function, the return value is set to a special value called None in Python. We’ll learn about what that value actually represents in a future course.

The entire process is shown in the animation below:

Python Tutor 4 Python Tutor 4

This review of how a function returns data should be very helpful as we continue to build more complex programs. Remember - we can always paste any Python code into a tool like Python Tutor to run through it step-by-step and see what it really does.

Subsections of Return

Worked Example

YouTube Video

Resources

Now that we’ve explored how to create functions, let’s work through a one of our previous worked examples to see how we could approach building that same program, but this time using some functions to simplify our code a bit.

Consider the following problem statement:

Write a program to print the sum of the first n prime numbers, where n is provided as input from the user.

This is a very simple problem statement, but it can be very complex to build a program that satisfies it. So, let’s go through the steps and see if we can get it to work.

Handling User Input

Now that we’ve learned about functions and the main() function, we can start with that structure. It is always good practice to include a main() function and a call to the main() function to our code, as shown here:

def main():


main()

Inside of the main() function, the first thing we’ll need to do is get input from the user, since we really can’t do anything else until we know what our goal is. While the problem statement doesn’t include any information about what inputs are acceptable, we can infer that only positive integers should be used as input. So, we can borrow the loop for reading a positive input from earlier to handle all of our input. This time, we’ll put it in a function called positive_input(), and this function will return the value input by the user once it is accepted:

def positive_input():
    x = int(input("Enter a positive integer: "))
    while x <= 0:
        print("Invalid input!")
        x = int(input("Enter a positive integer: "))
    return x


def main():
    n = positive_input()


main()

We’ll also add a call to that function in our main() function, and store the input provided by the user in the variable n inside of our main() function. Notice that the variable x in the positive_input() function is the value that is returned, but we are storing it in a different variable inside of the main() function. We’ll have to keep track of the location of that value as we think about our program. Thankfully, Python Tutor can help us learn how to do this, so feel free to follow along and build this program in Python Tutor and run it there to see how it works.

Building reusable functions, such as the positive_input() function we just created here, is a great way to build our programs. We can always think of previous code that we’ve already written and tested as possible building blocks for future programs, and reusing existing code is a great way to speed up our development process.

Prime Numbers

Next, we need some way to determine if a number is a prime number. Recall from mathematics that a prime number is a number that is only equally divisible by $ 1 $ and itself. For example, $ 7 $ is prime, but $ 8 $ is not since it can be evenly divided by $ 4 $.

Thankfully, we know that we can use the modulo operator % to determine if a number if evenly divisible by another number. So, all we need to do is check if each number less than our chosen number can equally divide it. If none of them can, then our chosen number is prime.

So, let’s write another function that performs this operation. We’ll start by creating a function that accepts our chosen number as a parameter:

def is_prime(n):

Next, we know that we need to check all of the numbers from $ 2 $ up to but not including n. We can do this using a for loop and the range() function:

def is_prime(n):
    for i in range(2, n):

Inside of the for loop, we want to check and see if the iterator variable i can evenly divide the chosen number n using the modulo operator:

def is_prime(n):
    for i in range(2, n):
        if n % i == 0:
            # i equally divides n

Here’s where things get a bit tricky - if i can equally divide n, we know that n is not prime and we don’t have to check any other values. So, in our function, we can just use the return False statement to return the value False from this function. As soon as the function reaches a return statement, even if it is inside of a loop, it will immediately stop the function and go back to where it was called from. In effect, we can use this to shortcut the rest of the loop.

def is_prime(n):
    for i in range(2, n):
        if n % i == 0:
            return False
    # what if we don't return false?

However, what happens if we check all of the values from $ 2 $ up to n and don’t find a single one that will equally divide our chosen number n? In that case, we’ll reach the end of our for loop, but our function hasn’t returned anything. So, we’ll need to add one extra line to the end of the function to return True if we get to that point.

def is_prime(n):
    for i in range(2, n):
        if n % i == 0:
            return False
    return True

There we go! That’s a quick and easy function to determine if a given number is prime. This function is a great example of a pattern that we’ll see many times as we write complex programs whenever we must determine if a particular situation is true for a given list of numbers. Inside of the loop, we’ll check each case and return False if it isn’t true. If it is true, then we’ll complete the entire loop without returning False, so we’ll need to return True at the end of the function. We’ll see this pattern again in a later lab.

Complete Program

Finally, now that we have the is_prime() function, we can complete our main() function by simply iterating through all possible numbers until we’ve found n prime numbers, and then print the sum:

def main():
    n = positive_input()
    count = 0
    i = 2
    sum = 0
    while count < n:
        if is_prime(i):
            sum = sum + i
            count = count + 1
        i = i + 1
    print(f"The sum of the first {n} prime numbers is {sum}")

This program requires three additional variables. The variable count is used to keep track of the number of prime numbers found, so we’ll increment it inside of the if statement each time we find a prime number. Likewise, the sum variable keeps track of the sum of the prime numbers, which we’ll print at the end. Finally, we use i as our iterator variable, so we must make sure that we increment i each time the while loop iterates, outside of the if statement. We’ll start i at $ 2 $, since $ 1 $ is not a prime number mathematically. A very common programming mistake is to forget to increment i outside the if statement, resulting in an infinite loop. Also, we chose to use a while loop instead of a for loop since our program’s goal is to sum up the first n prime numbers, which is better expressed as a while loop instead of a for loop.

The full program is shown below:

def positive_input():
    x = int(input("Enter a positive integer: "))
    while x <= 0:
        print("Invalid input!")
        x = int(input("Enter a positive integer: "))
    return x


def is_prime(n):
    for i in range(2, n):
        if n % i == 0:
            return False
    return True


def main():
    n = positive_input()
    count = 0
    i = 2
    sum = 0
    while count < n:
        if is_prime(i):
            sum = sum + i
            count = count + 1
        i = i + 1
    print(f"The sum of the first {n} prime numbers is {sum}")


main()

Finally, notice that this program doesn’t contain a loop nested within another loop in the same function, but because we are calling the is_prime() function, which contains a loop, from within a loop inside of the main() function, it actually has a nested loop structure inside of it! Feel free to run this program in Python or using Python Tutor to confirm that it works as expected.

Subsections of Worked Example

Functions Practice

Let’s try some simple practice problems. These problems are not graded - they are just for you to practice before doing the real exercises in the lab itself. You can find the answers below each question by clicking the button below each question.

6.1 Reading Code

Write the output that is displayed to the user after running the following Python program:

def foo(word):
    print(word, end=" ")


def bar(word1, word2, word3):
    foo(word1)
    foo(word2)
    print("don't make a", end=" ")
    print(word3)


def main():
    bar("two", "wrongs", "right")


main()
6.1 Answer

The correct answer is:

two wrongs don't make a right

6.2 Writing Code

Construct a function (not an entire program) that meets the following specification. When the function is called using this line of code:

fun("s", "e", "l" "r")

it should produce the following output:

seller

When the same function is called using this line of code:

fun("p", "i", "p", "n")

it should produce the following output:

pippin
6.2 Answer

One possible answer is shown below:

def fun(one, two, three, four):
    print(one, end="")
    print(two, end="")
    print(three, end="")
    print(three, end="")
    print(two, end="")
    print(four, end="")

There are many possible answers!

6.3 Writing Code

Write a complete program in Python that meets the following specification:

  1. It should include a function named display that requires a single parameter. When run, that function should print the value of the parameter to the terminal, but without a newline character at the end.
  2. It should include a function named square that will call the display function multiple times to create an ASCII art square as shown in the output example below. The square procedure may not use the print(expression) statement directly - it must call the display function to produce output.
  3. It should include a main function that only calls the square function.
  4. It should call the main function at the end of the code to start the program.

When the program is run, it should produce the following output:

* * * *
* * * *
* * * *
* * * *
6.3 Answer

One possible solution is given below:

def display(line):
    print(line, end="")


def square():
    display("* * * *\n")
    display("* * * *\n")
    display("* * * *\n")
    display("* * * *\n")


def main():
    square()


main()

Many others are possible!

Summary

In this lab, we introduced several major important topics in Python. Let’s quickly review them.

Python Functions

A function in Python is a piece of code that is given a name, which can then be run by calling it elsewhere in the program.

To create a function, we use the following structure:

def function_name(parameter1, parameter2):
    <block of statements>
  1. Function names follow the same rules as variable names.
  2. Functions may require 0 or more parameters, placed in parentheses after the function name. Multiple parameters are separated by commas.
  3. When called, a function will run the lines of code indented beneath the function definition.

To call a function, we use the following structure:

function_name(argument1, argument2)
  1. A function call is the function name, followed by parentheses ().
  2. Inside of the parentheses, a matching argument must be included for each parameter required by the function. Multiple arguments are separated by commas.
  3. Each argument may be an expression that evaluates to a value.
  4. The values of each argument are copied into the function. When the function ends, the original arguments are unchanged.

Returning Data from Functions

  • return expression will return the value of expression to where the function was called from.
  • Function calls can be used in assignment statements to store return values, or as part of other expressions.