Writing Functions#
Questions:#
How can I create my own functions?
Objectives:#
Explain and identify the difference between function definition and function call.
Write a function that takes a small, fixed number of arguments and produces a single result.
Keypoints:#
Break programs down into functions to make them easier to understand.
Define a function using
def
with a name, parameters, and a block of code.Defining a function does not run (execute) it.
Arguments in a function call are matched to its defined parameters.
Functions may return a result to their caller using
return
.
Break programs down into functions to make them easier to understand.#
Human beings can only keep a few items in working memory at a time.
Functions help us simplify complex ideas/tasks by breaking them down into smaller components
Encapsulate complexity so that we can treat it as a single “thing”.
Also enables re-use.
Write one time, use many times.
Functions are one of the fundamental building blocks of Python programs. Programs can range from just a handful of lines of code to literally millions of lines of code. We manage the complexity of data and of problems that we want to solve by “decomposing them” (breaking them down) into smaller pieces. The programs that we design to work with these data and solve problems are similarly decomposed into small functions - each function solving just one of the smaller tasks that make up the problem at hand.
Functions also help to avoid duplication of code and can therefore make programs smaller and easier to understand. For example, you may need to perform identical mathematical operations on multiple sets of inputs. You could write these lines multiple times in your program, but this is prone to errors. If you made an error when writing this code, it’s likely that the error would be copied into multiple parts of your program. Instead, it’s better to define a function that can complete the operation and then call the function each time you need to perform that operation.
A motivating example#
Let’s work through a small example. Suppose we find ourselves repeatedly needing to compute the average of three numbers. So, at many places in our code, we have expressions like:
(2 + 3 + 4) / 3
(-1 + 7 + 5) / 3
(2 + 12 + 3) / 3
Note that these expressions are similar. They all look like:
\((\alpha + \beta + \gamma)/3\)
where I’ve simply used Greek letters as placeholders for numbers. Having identified these placeholders (or “points of variation”) in the expressions above, we can define a function that takes a value for each of these placeholders as input. In Python, we use def
to define a new function:
def average_of_three(a, b, c):
return (a + b + c) / 3
Here, a
, b
, and c
are called parameters - think of a parameter as a kind of variable that is assigned a value when the function is called. The function body is all the code (in this case, just one line) that is indented after the first. Note that in this case it returns the average of the values of a
, b
, and c
.
We call a function using its name followed by a value for each of the parameters that the function expects:
average_of_three(-1, 7, 6)
When we evaluate this expression, the value -1 is assigned to the parameter a
, 7 to b
, and 5 to c
. We then evaluate the body of the function:
return (-1 + 7 + 6) / 3
which returns the value 4 to the point in the code at which the function was called. Note that the body of a function is not evaluated unless the function is called.
return
is a special keyword in Python. If there is a value to the right of the keyword, that value is returned to the function’s caller. If there is no value to the right of the keyword return, the special value None
is returned to the function’s caller. When a return statement is executed in Python, its value (or None) is returned to the function’s caller immediately and no further lines of code in the function are executed.
Following the interim summary below, we will review, and expand upon, the above points on writing functions.
Interim Summary: Define a function using def
with a name, parameters, and a block of code.#
Begin the definition of a new function with
def
.Followed by the name of the function.
Must obey the same rules as variable names.
Then parameters in parentheses.
Empty parentheses if the function doesn’t take any inputs.
We will discuss this in detail in a moment.
Then a colon.
Then an indented block of code.
Defining a function does not run it.#
Defining a function does not run it.
Like assigning a value to a variable.
Must call the function to execute the code it contains.
def print_greeting():
print('Hello!')
print('The weather is nice today.')
print('Right?')
print_greeting()
Hello!
The weather is nice today.
Right?
Arguments in a function call are matched to its defined parameters.#
Functions are most useful when they can operate on different data.
Specify parameters when defining a function.
These become variables when the function is executed.
Are assigned the arguments in the call (i.e., the values passed to the function).
If you don’t name the arguments when using them in the call, the arguments will be matched to parameters in the order the parameters are defined in the function.
def print_date(year, month, day):
joined = str(year) + '/' + str(month) + '/' + str(day)
print(joined)
print_date(1871, 3, 19)
1871/3/19
Or, we can name the arguments when we call the function, which allows us to specify them in any order and adds clarity to the call site; otherwise as one is reading the code they might forget if the second argument is the month or the day for example.
print_date(month=3, day=19, year=1871)
1871/3/19
Via Twitter:
()
contains the ingredients for the function while the body contains the recipe.
Functions may return a result to their caller using return
.#
Use
return...
to give a value back to the caller.May occur anywhere in the function.
But functions are easier to understand if
return
occurs:At the start to handle special cases.
At the very end, with a final result.
def average(values):
if len(values) == 0:
return None
return sum(values) / len(values)
a = average([1, 3, 4])
print('average of actual values:', a)
average of actual values: 2.6666666666666665
print('average of empty list:', average([]))
average of empty list: None
Remember: every function returns something.
A function that doesn’t explicitly
return
a value automatically returnsNone
.
result = print_date(1871, 3, 19)
print('result of call is:', result)
1871/3/19
result of call is: None
Identifying Syntax Errors#
Read the code below and try to identify what the errors are without running it.
Run the code and read the error message. Is it a
SyntaxError
or anIndentationError
?Fix the error.
Repeat steps 2 and 3 until you have fixed all the errors.
def another_function
print("Syntax errors are annoying.")
print("But at least python tells us about them!")
print("So they are usually not too hard to fix.")
Cell In[10], line 1
def another_function
^
SyntaxError: invalid syntax
Definition and Use#
What does the following program print?
def report(pressure):
print('pressure is', pressure)
print('calling', report, 22.5)
calling <function report at 0x7fcb4f0113f0> 22.5
Click the button to reveal the solution
A function call always needs parentheses, otherwise you get the memory address of the function object. So, if we wanted to call the function named report
and give it the value 22.5 to report on, we could have our function call as in the following cell…
print("calling")
report(22.5)
calling
pressure is 22.5
Order of Operations#
What’s wrong in this example?
result = print_time(11, 37, 59)
def print_time(hour, minute, second):
time_string = str(hour) + ':' + str(minute) + ':' + str(second)
print(time_string)
Cell In [11], line 4
time_string = str(hour) + ':' + str(minute) + ':' + str(second)
^
IndentationError: expected an indented block after function definition on line 3
After fixing the problem above, explain why running this example code gives the output that it does:
result = print_time(11, 37, 59)
print('result of call is:', result)
Why is the result of the call
None
?
Click the button to reveal the solution
The problem with the example is that the function
print_time()
is defined after the call to the function is made. Python doesn’t know how to resolve the nameprint_time
since it hasn’t been defined yet and will raise aNameError
e.g.,NameError: name 'print_time' is not defined
The first line of output
11:37:59
is printed by the first line of code,result = print_time(11, 37, 59)
that binds the value returned by invokingprint_time
to the variableresult
. The second line is from the second print call to print the contents of theresult
variable.print_time()
does not explicitlyreturn
a value, so it automatically returnsNone
.
Find the First#
Fill in the blanks to create a function that takes a list of numbers as an argument and returns the first negative value in the list. What does your function do if the list is empty? What if the list has no negative numbers?
def first_negative(values):
for v in ____:
if ____:
return ____
Click the button to reveal the solution
def first_negative(values):
for v in values:
if v < 0:
return v
If an empty list or a list with all positive values is passed to this function, it returns None
:
my_list = []
print(first_negative(my_list))
Calling by Name#
Earlier we saw this function:
def print_date(year, month, day):
joined = str(year) + '/' + str(month) + '/' + str(day)
print(joined)
We saw that we can call the function using named arguments, like this:
print_date(day=1, month=2, year=2003)
What does
print_date(day=1, month=2, year=2003)
print?When and why is it useful to call functions this way?
Click the button to reveal the solution
2003/2/1
Using named arguments can make code more readable since one can see from the function call what name the different arguments have inside the function. It can also reduce the chances of passing arguments in the wrong order, since by using named arguments the order doesn’t matter.
Encapsulation of an If/Print Block#
The code below will run on a label-printer for chicken eggs. A digital scale will report a chicken egg’s mass (in grams) to the computer and then the computer will print a label.
import random
for i in range(10):
# simulating the mass of a chicken egg
# the (random) mass will be 70 +/- 20 grams
mass = 70 + 20.0 * (2.0 * random.random() - 1.0)
print(mass)
# egg sizing machinery prints a label
if mass >= 85:
print("jumbo")
elif mass >= 70:
print("large")
elif mass < 70 and mass >= 55:
print("medium")
else:
print("small")
The if-block that classifies the eggs might be useful in other situations,
so to avoid repeating it, we could fold it into a function, get_egg_label()
.
Revising the program to use the function would give us this:
# revised version
import random
for i in range(10):
# simulating the mass of a chicken egg
# the (random) mass will be 70 +/- 20 grams
mass = 70 + 20.0 * (2.0 * random.random() - 1.0)
print(mass, get_egg_label(mass))
Create a function definition for
get_egg_label()
that will work with the revised program above. Note that theget_egg_label()
function’s return value will be important. Sample output from the above program would be71.23 large
.A dirty egg might have a mass of more than 90 grams, and a spoiled or broken egg will probably have a mass that’s less than 50 grams. Modify your
get_egg_label()
function to account for these error conditions. Sample output could be25 too light, probably spoiled
.
Click the button to reveal the solution
def get_egg_label(mass):
# egg sizing machinery prints a label
egg_label = "Unlabelled"
if mass >= 90:
egg_label = "warning: egg might be dirty"
elif mass >= 85:
egg_label = "jumbo"
elif mass >= 70:
egg_label = "large"
elif mass < 70 and mass >= 55:
egg_label = "medium"
elif mass < 50:
egg_label = "too light, probably spoiled"
else:
egg_label = "small"
return egg_label
This chapter has been adapted from Software Carpentry’s Plotting and Programming in Python workshop and materials UBC’s CPSC 103 course as taught by Prof. Meghan Allen.