# Functions
# Learning Objectives
After completing this lesson, you will be able to:
- Organize code into functions
- Use functions for encapsulation and abstraction
- Reduce repetition of code
# Lesson
# Overview
Functions are one of the most useful tools in a programmer's toolkit. In the same way that variables are reusable labels for individual values, functions are reusable labels for a set of statements.
In the previous lesson, you learned how to write loops that iterate through sequences. The body of your loops performed a specific task, such printing a to-do list or marking a to-do item as complete.
In this lesson, you will improve on the to-do list by eliminating redundant code by defining functions.
# What is a function?
A function is like a recipe. It gives you a list of ingredients. After all the steps are carried out, the result is a specific dish.
(diagram of food going "into" a recipe card by getting mapped onto the ingredients list, then resulting dish being returned by the recipe)
An example of a function is the print() function.
The ingredient that it wants is a String (or something it can get a String version of). The result is output to the screen.
Let's imagine using an add() function that adds two numbers.
result = add(2, 3)
print(result)
2
Here is the output from running that code:
5
In our simple example, our "ingredients" are the numbers 2 and 3. The "dish" that is produced is the number 5.
Now that you have an idea of what a function does, it's time to build one.
# How do I write a function?
Here is the add() function mentioned above:
def add(num1, num2):
return num1 + num2
2
# A function is defined in four parts
(annotated diagram a screenshot of our code.)
In Python, you create a function by:
- Using the
defkeyword - Naming the function
- Declaring parameters ("ingredients")
- Providing the body of the function as a code block
# How do I run a function?
After defining a function, you run the function by using its name, followed by a pair of parentheses.
When you run a function, each statement in the functions body is run.
TIP
Programmers refer to running a function as calling, executing, or invoking a function.
A function call, or invocation is the act of running the body of a function, using any values that might have been passed in as arguments.
# How do I replace repetitive code with function calls?
Now it's time to put your understanding of functions to use. Update your to-do program by adding a print_todos() function. You will use that instead of repeating the for-loop that print()s your to-dos.
Printing to-dos with a function
todos = []
# Create a constant for our main menu.
# This saves us from having to type it out twice.
MAIN_MENU = """
Choose an action:
P: Print your to-do list
A: Add a to-do item
R: Replace a to-do item
C: Complete a to-do item
(Or press Enter to exist the program.)
"""
def print_todos():
# Print the current list of to-do items
print("\n\n\nTo do:")
print("====================")
count = 1
for todo in todos:
print("%d: %s" % (count, todo))
count += 1
choice = input(MAIN_MENU)
choice = choice.upper() # Simplifies our if-conditions
# As long as they type something, keep prompting
while len(choice) > 0:
if choice == "P":
print_todos()
elif choice == "A":
new_todo = input("What do you need to do? ")
if len(new_todo) > 0:
todos.append(new_todo)
elif choice == "R":
print_todos()
which_index = input("Which to-do number? ")
try:
which_index = int(which_index)
which_index -= 1 # Convert from human-readable to 0-based index
if which_index >= 0 and which_index < len(todos):
new_todo = input("What do you need to do? ")
todos[which_index] = new_todo
except ValueError:
print("\n\n***Please enter a number.***")
elif choice == "C":
print_todos()
which_index = input("Which to-do number? ")
try:
which_index = int(which_index)
which_index -= 1 # Convert from human-readable to 0-based index
if which_index >= 0 and which_index < len(todos):
completed_todo = todos[which_index]
del todos[which_index]
print("%s has been marked complete!" % completed_todo)
except ValueError:
print("\n\n***Please enter a number.***")
else:
print("\n\n***Please enter a valid menu option.***")
choice = input(MAIN_MENU)
choice = choice.upper() # Simplifies our if-conditions
print("Have a nice day!")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
We define the new print_todos() function near the top of our program, before we try to call it. In our while-loop, we call print_todos() any time we want to show the user their current list of to-dos.
TIP
Improving code by removing repetition, improving readability, and increasing flexibility are examples of refactoring.
# How do I return a value from a function?
Our add() function returns a value, allowing us to use it on the RHS of an assignment.
TIP
Functions are not required to return a value, but it is best to write functions that do.
Functions that return a value based on their inputs are easier to test and debug.
# How do I use return values to "signal" success or failure?
In our to-do program, we prompt the user for the number of an existing to-do in two places: when they replace a to-do and when they complete a to-do.
Unlike replacing the repetitive for-loops with print_todos(), the code is not exactly the same. Prompting the user and checking its validity are the same, but the meat of our code (replacing or completing) is buried inside of an if, which is inside of a try.
One option is to write a function that only handles prompting for a number and checking if it's valid. It can return the index if it's valid and return -1 if it's not.
TIP
-1 is commonly used to indicate that an index is invalid. This convention is popular in a number of languages, including Python and Java.
Writing a `get_todo_index()` function
We refactor some of the old code into a new get_todo_index() function. Inside the function, if we determine that the index is valid, we return it. If it is invalid (either they didn't type a number or the number fell outside the range of valid indices), we return -1.
Knowing that get_todo_index() will only return -1 or a valid index lets us simplify the code for replacing a to-do.
todos = []
# Create a constant for our main menu.
# This saves us from having to type it out twice.
MAIN_MENU = """
Choose an action:
P: Print your to-do list
A: Add a to-do item
R: Replace a to-do item
C: Complete a to-do item
(Or press Enter to exist the program.)
"""
def print_todos():
# Print the current list of to-do items
print("\n\n\nTo do:")
print("====================")
count = 1
for todo in todos:
print("%d: %s" % (count, todo))
count += 1
def get_todo_index():
which_index = input("Which to-do number? ")
try:
which_index = int(which_index)
which_index -= 1 # Convert from human-readable to 0-based index
if which_index >= 0 and which_index < len(todos):
return which_index
else:
return -1
except ValueError:
print("\n\n***Please enter a number.***")
return -1
choice = input(MAIN_MENU)
choice = choice.upper() # Simplifies our if-conditions
# As long as they type something, keep prompting
while len(choice) > 0:
if choice == "P":
print_todos()
elif choice == "A":
new_todo = input("What do you need to do? ")
if len(new_todo) > 0:
todos.append(new_todo)
elif choice == "R":
print_todos()
which_index = get_todo_index()
if which_index != -1:
new_todo = input("What do you need to do? ")
todos[which_index] = new_todo
elif choice == "C":
print_todos()
which_index = input("Which to-do number? ")
try:
which_index = int(which_index)
which_index -= 1 # Convert from human-readable to 0-based index
if which_index >= 0 and which_index < len(todos):
completed_todo = todos[which_index]
del todos[which_index]
print("%s has been marked complete!" % completed_todo)
except ValueError:
print("\n\n***Please enter a number.***")
else:
print("\n\n***Please enter a valid menu option.***")
choice = input(MAIN_MENU)
choice = choice.upper() # Simplifies our if-conditions
print("Have a nice day!")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
We can reuse get_todo_index() for completing a to-do.
todos = []
# Create a constant for our main menu.
# This saves us from having to type it out twice.
MAIN_MENU = """
Choose an action:
P: Print your to-do list
A: Add a to-do item
R: Replace a to-do item
C: Complete a to-do item
(Or press Enter to exist the program.)
"""
def print_todos():
# Print the current list of to-do items
print("\n\n\nTo do:")
print("====================")
count = 1
for todo in todos:
print("%d: %s" % (count, todo))
count += 1
def get_todo_index():
which_index = input("Which to-do number? ")
try:
which_index = int(which_index)
which_index -= 1 # Convert from human-readable to 0-based index
if which_index >= 0 and which_index < len(todos):
return which_index
else:
return -1
except ValueError:
print("\n\n***Please enter a number.***")
return -1
choice = input(MAIN_MENU)
choice = choice.upper() # Simplifies our if-conditions
# As long as they type something, keep prompting
while len(choice) > 0:
if choice == "P":
print_todos()
elif choice == "A":
new_todo = input("What do you need to do? ")
if len(new_todo) > 0:
todos.append(new_todo)
elif choice == "R":
print_todos()
which_index = get_todo_index()
if which_index != -1:
new_todo = input("What do you need to do? ")
todos[which_index] = new_todo
elif choice == "C":
print_todos()
which_index = get_todo_index()
if which_index != -1:
completed_todo = todos[which_index]
del todos[which_index]
print("%s has been marked complete!" % completed_todo)
else:
print("\n\n***Please enter a valid menu option.***")
choice = input(MAIN_MENU)
choice = choice.upper() # Simplifies our if-conditions
print("Have a nice day!")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# How do I return multiple values?
You are only allowed to return a single value from a function. To get around this constraint, you can return a sequence instead.
Here's an example of a divide() function that returns both the quotient and the remainder:
def divide(dividend, divisor):
quotient = dividend // divisor # The // operator rounds
remainder = dividend % divisor
return (quotient, remainder) # Return a tuple
result = divide(5, 3)
print(result)
2
3
4
5
6
7
The function appears to return multiple values, but it is really just a single tuple:
(1, 2)
# What if there is no return?
If you do not include a return statement or if you use the return keyword by itself, Python interprets this as returning the value None.
def return_nothing():
return
result = return_nothing()
print(result)
2
3
4
5
When you run this code, the None keyword is printed:
None
As the name implies, None signifies "no value".
# Does any code run after a return statement?
When Python encounters the return keyword while running a function, it exits the functions. This means that none of the statements after return will run.
def does_not_print():
print("This prints")
return
print("This does not print")
print("Nor does this")
print("Third time is not a charm")
print(":)")
does_not_print()
2
3
4
5
6
7
8
9
10
The first print() runs, but the rest do not:
This prints
# What are arguments?
We have described functions as reusable recipes, or a series of steps.
Not only are they reusable, but you can "configure" their behavior by passing arguments when you call the function.
Keeping with our recipe analogy, each argument is a particular ingredient. If we had a sandwich() function, we could configure it by passing in "turkey" to make turkey sandwich, or "veggie" to make a veggie sandwich.
In some of the examples in this lesson, you have already seen arguments in action. When you define a function, the argument names are listed between the parentheses. When you call a function, you provide argument values.
In our simple add() function, we listed two arguments, num1 and num2:
def add(num1, num2):
return num1 + num2
2
In the body of the function, we use arguments the same way we use variables. In both cases, they are just labels for values.
When we call a function, we provide the values that get assigned to the argument names:
result = add(2, 3)
print(result)
2
TIP
Though they are technically different, programmers often use the terms "parameter" and "argument" interchangeably.
Parameters (or the "formal parameter list") is part of a function's definition.
Arguments are the values provided when a function is called. These are the values that are used while the function body is running.
# Are arguments required?
If you call a function but fail to provide the correct number of arguments, an error will be thrown.
result = add(2)
print(result)
2
We defined the add() function with 2 arguments, but only passed 1. This results in a TypeError:
Traceback (most recent call last):
File "add-2.py", line 1, in <module>
result = add(2)
TypeError: add() missing 1 required positional argument: 'num2'
2
3
4
Thankfully, Python makes it clear which arguments are missing.
# Does the order of arguments matter?
When you call a function, the order of the arguments must be the same as the function definition.
Here is a variation of our "greeter" program:
def make_formal_greeting(name, title):
return "This is %s, the %s!" % (name, title)
result = make_formal_greeting("Rob Stark", "King in the North")
print(result)
oops = make_formal_greeting("King in the North", "Rob Stark")
print(oops)
2
3
4
5
6
7
8
The first time we call make_formal_greeting(), we match the order of arguments. The second time, we do not.
This is Rob Stark, the King in the North!
This is King in the North, the Rob Stark!
2
Though Python does not throw an Error, the output is not quite right.
# What is scope?
Scope is the amount of code where a variable can be accessed.
The two main kinds of scope are local and global
# What is local scope?
Local scope is when a variable is defined as part of a function body, or as one of its arguments.
A local variable (i.e., one with local scope) can only be accessed inside of the function where it is defined.
def dance():
kind = "silly"
print("I am doing a %s dance" % kind)
dance()
print(kind)
2
3
4
5
6
7
When we call dance(), the print() statement on line 3 accesses the kind variable. This is OK because kind is local to the dance() function.
On line 7, if we try to print(kind), we get an error because kind does not exist outside of the dance() function.
I am doing a silly dance
Traceback (most recent call last):
File "scope_1.py", line 7, in <module>
print(kind)
NameError: name 'kind' is not defined
2
3
4
5
Likewise, the local scope of one function has no access to the local scope of another function.
def dance():
kind = "silly"
print("I am doing a %s dance" % kind)
def dance2():
print("I want to do a %s dance" % kind)
dance()
dance2()
2
3
4
5
6
7
8
9
dance() can access kind, but dance2() cannot:
I am doing a silly dance
Traceback (most recent call last):
File "scope_2.py", line 9, in <module>
dance2()
File "scope_2.py", line 6, in dance2
print("I want to do a %s dance" % kind)
NameError: name 'kind' is not defined
2
3
4
5
6
7
TIP
Every function has its own local scope.
# Do parameters and local variables have the same scope?
You can think of a parameter as a local variable whose assigned value comes from the "outside" of the function body.
def dance(kind="silly"):
print("I am doing a %s dance" % kind)
dance("funky") # Totally OK.
print(kind) # Error!
2
3
4
5
Therefore, the scope of a parameter and a local variable are the same.
I am doing a funky dance
Traceback (most recent call last):
File "scope_3.py", line 5, in <module>
print(kind) # Error!
NameError: name 'kind' is not defined
2
3
4
5
Trying to access a parameter outside the scope of a function causes an error.
# What is global scope?
The other kind of scope is the scope of the entire program file, known as global scope.
kind = "mesmerizing"
def dance():
print("I am doing a %s dance" % kind)
dance() # Totally OK.
print(kind) # Also totally OK.
2
3
4
5
6
Therefore, the scope of a parameter and a local variable are the same.
I am doing a mesmerizing dance
mesmerizing
2
TIP
It is best to use local variables instead of global variables. Overuse of global variables is a sign of poor organization and can lead to buggy programs.
WARNING
Python does not normally allow you to modify global variables from inside your functions. Check out the "Scope of Variables in Python Tutorial" link in the <#additional-resources> at the end of the lesson.
# What is recursion?
A function that calls itself is a recursive function. Recursion is an alternative to using a loop.
Here is a loop that we can convert to a recursive function:
def calculate_sum(number_list):
result = 0
for number in number_list:
result += number
return result
the_sum = calculate_sum(range(100))
print(the_sum)
2
3
4
5
6
7
8
When using a loop, your function goes like this:
- Create a variable for the
resultand set it to0 - For each
numberin the list add it toresult - Then return
result
With recursion, you would approach the problem differently. Your function description is:
- The sum of the list is the first number plus the sum of the rest of the list
- If there are no more items in the list, we're finished
This is what that description looks like in code:
def calculate_sum(number_list):
if len(number_list) == 0:
return 0
else:
return number_list[0] + calculate_sum(number_list[1:])
the_sum = calculate_sum(range(100))
print(the_sum)
2
3
4
5
6
7
8
On line 2, we check for the base case - this is how the function knows that it is finished.
On line 5 is the recursive definition: the first number plus the sum of the rest of the list. We return the result of adding number_list[0] to the result of calling calculate_sum() again. But when we call calculate_sum(), we pass it everything except number_list[0].
That is, each time we call calculate_sum(), we pass it a shorter List. Eventually, the final time we call calculate_sum(), it receives a List whose len() is 0.
TIP
Recursion can be tricky to understand at first. It is a useful technique if you can describe a solution in simple operation that can be applied to a smaller and smaller set of values.
But, most programming languages can only handle recursion over relatively small sets of values (typically tens of thousands). For very large sets, you will need to use loops.
To write recursive functions, make sure that:
- You define the base case
- Your alternate case includes a recursive function call
- The set of values gets smaller each time
For more information about recursion, check out the Additional Resources section below.
# Summary
In this lesson, you learned how to:
- Define reusable functions
- Return values
- Specify arguments
- Work with local and global scope
- Do basic recursion
TIP
Besides serving as building blocks for your programs, it is useful to think of functions as communicating with each other via arguments and return values.
# Training Exercises
To solidify your knowledge, here are a set of exercises that will require you to use the techniques you've just learned in the lesson above.
They are organized into small, medium, and large sized problems. The small exercises will be very similar to the examples in the lesson. If you get stuck, refer to the relevant section above. The medium exercises will require you to combine concepts. The lesson may not have a single, specific example for you to reference. The large exercises are more open-ended and may require you to search the web for additional material.
# Small
# 1. Madlib function
Write a function that accepts two arguments: a name and a subject.
The function should return a String with the name and subject interpolated in.
For example:
madlib("Jenn", "science")
# "Jenn's favorite subject is science."
madlib("Jeff", "history")
# "Jeff's favorite subject is history."
2
3
4
5
Provide default arguments in case one or both are omitted.
# 2. Celsius to Fahrenheit conversion
The formula to convert a temperature from Celsius to Fahrenheit is:
F = (C * 9/5) + 32
Write a function that takes a temperature in Celsius, converts it Fahrenheit, and returns the value.
# 3. Fahrenheit to Celsius conversion
The formula to convert a temperature from Fahrenheit to Celsius is:
C = (F - 32) * 5/9
Write a function that takes a temperature in Fahrenheit, converts it to Celsius, and returns the value.
# 4. is_even function
Write a function that accepts a number as an argument and returns a Boolean value. Return True if the number is even; return False if it is not even.
# 5. is_odd function
Write an is_odd function that uses your is_even function to determine if a number is odd. (That is, do not do the calculation - call a function that does the calculation.)
Hint: You'll use the not keyword.
# 6. only_evens function
Write a function that accepts a List of numbers as an argument.
Return a new List that includes the only the even numbers.
only_evens([11, 20, 42, 97, 23, 10])
# Returns [20, 42, 10]
2
Hint: use your is_even() function to determine which numbers to include in your new List.
# 7. only_odds function
Write a function that accepts a List of numbers as an argument.
Return a new List that includes the only the odd numbers.
only_odds([11, 20, 42, 97, 23, 10])
# Returns [11, 97, 23]
2
Hint: use your is_odd() function to determine which numbers to include in your new List.
# Medium
# 1. Find the smallest number
Write a function smallest that accepts a List of numbers as an argument.
It should return the smallest number in the List.
# 2. Find the largest number
Write a function largest that accepts a List of numbers as an argument.
It should return the largest number in the List.
# 3. Find the shortest String
Write a function shortest that accepts a List of Strings as an argument.
It should return the shortest String in the List.
# 4. Find the longest String
Write a function longest that accepts a List of Strings as an argument.
It should return the longest String in the List.
# Large
# 1. Tic-tac-toe
Write a function move that accepts three arguments:
boarda 2-dimensional array that represents a 3x3 tic-tac-toe boardlocationa 2-item tuple that specifies an cell on theboardplayera String, either"X"or"Y"
Return a copy of the board with the player String placed at the location.
Throw an error if:
- The
boardis the wrong size - The
locationis already occupied by a player - The
locationis invalid - The
playerString is something other than"X"or"Y"
# 2. Change maker
You will write a function that calculates how many bills and coins someone would receive as change.
Write a function make_change that accepts two arguments:
total_charge- the amount of money owedpayment- the amount of money payed
Return a 2-dimensional tuple whose values represent the bills and coins.
For example, consider the following tuple:
(
(3, 0, 1, 1, 0, 1),
(4, 1, 0, 2)
)
2
3
4
The first item represents the bills:
- 3 singles
- 0 fives
- 1 ten
- 1 twenty
- 0 fifties
- 1 hundred
The second item represents the coins
- 4 pennies
- 1 nickel
- 0 dimes
- 2 quarters
Hint: consider writing a small function to help produce the "bills" tuple and another function to help produce the "coins" tuple.
# 3. Calculate the change value
Write a value_of_change function that accepts a 2-dimensional tuple like the one returned by the make_change function.
This function should calculate the monetary value specified by the tuple.
For example, if the following tuple were passed to value_of_change
(
(3, 0, 1, 1, 0, 1),
(4, 1, 0, 2)
)
2
3
4
It would return 133.59
As before, consider writing helper functions to deal with the bills and the coins separately.
# Interview Questions
# Fundamentals
- What is a function?
- What is the purpose of arguments?
- Why should functions return values?
# Bugfix
(There is no bugfix interview question for this lesson.)
# Conceptual
What is recursion? When should it be used? When should it not be used?
What is the call stack?
# Architect
Using your knowledge of functions and loops write a program that prints lyrics in the following format:
99 bottles of ginger beer on the wall. 99 bottles of ginger beer.
98 bottles of ginger beer on the wall. 98 bottles of ginger beer.
...
1 bottle of ginger beer on the wall. 1 bottle of ginger beer.
No bottles of ginger beer on the wall. no bottles of ginger beer.
2
3
4
5
Make sure your code includes a function bottleLine() that accepts a number (e.g. 99), and returns a single line of the song (e.g., "99 bottles of ginger beer on the wall. 99 bottles of ginger beer.")
Note that for the last two lines, the line is formatted differently ("1 bottle" instead of "1 bottles" and "No bottles" instead of "0 bottles").