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.

What's the difference between parameters and arguments? Parameters are the variables listed in the function's definition inside the parentheses. Arguments are the actual values passed to the function. In the average_of_three function definition, a, b, and c are the parameters. The numbers we want to find the average of in average_of_three are the arguments we pass to the function. In everyday usage, however, I find that people often use "parameter" and "argument" interchangeably and that this is usually not an issue.

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
result = print_date(1871, 3, 19)
print('result of call is:', result)
1871/3/19
result of call is: None

Identifying Syntax Errors#

  1. Read the code below and try to identify what the errors are without running it.

  2. Run the code and read the error message. Is it a SyntaxError or an IndentationError?

  3. Fix the error.

  4. 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
print("calling")
report(22.5)
calling
pressure is 22.5

Order of Operations#

  1. 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
  1. 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)
  1. Why is the result of the call None?

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 ____

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)
  1. What does print_date(day=1, month=2, year=2003) print?

  2. When and why is it useful to call functions this way?

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))

  1. Create a function definition for get_egg_label() that will work with the revised program above. Note that the get_egg_label() function’s return value will be important. Sample output from the above program would be 71.23 large.

  2. 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 be 25 too light, probably spoiled.


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.