Creating Functions

Overview:

  • Teaching: 10 min
  • Exercises: 10 min

Questions

  • What is a function?
  • How can I define new functions?
  • What’s the difference between defining and calling a function?
  • What happens when I call a function?

Objectives

  • Define a function that takes parameters.
  • Return a value from a function.
  • Test and debug a function.
  • Set default values for function parameters.
  • Explain why we should divide programs into small, single-purpose functions.

So far we have looked how to use variables to perform calculations, and explored loops and conditionals which form the basic building blocks of programming. But what we would like to do is get to the stage where we can write blocks of functional code to perform a specific task, since cutting and pasting it is going to make our code very long and very repetitive, very quickly. We’d like a way to package our code so that it is easier to re-use, and Python provides for this by letting us define things called 'functions' a shorthand way of re-executing longer pieces of code. Let’s start by defining a function that does what all good first examples should do, display "Hello World!".

In [1]:
def print_greeting():
    print('Hello World!')

When we create a function and "run" it, nothing happens. This is sensible because what we want to do is create the function once, and then run it when we want to execute the code. We do this by calling the function:

In [2]:
print_greeting()
Hello World!

This is all very well but it's unlikely that this is a function that we are going to have much use for. Let's write a function fahr_to_celsius that converts temperatures from Fahrenheit to Celsius, and then we'll examine its structure:

In [3]:
def fahr_to_celsius(temp):
    return ((temp - 32) * (5/9))

Let's examine the different parts of our function:

Anatomy of a python function

The function definition opens with the keyword def followed by the name of the function fahr_to_celsius and a parenthesized list of parameter (or argument) names temp. The body of the function - the statements that are executed when it runs - is indented below the definition line. The body concludes with the keyword return followed by the value the function calculates.

When we call the function, the values we pass to it are assigned to those variables so that we can use them inside the function. Inside the function, we use a return statement to send a result back to whoever asked for it.

Let's try running the function...

In [4]:
fahr_to_celsius(32)
Out[4]:
0.0

This command should call our function, using “32” as the input and return the function value.

In fact, calling our own function is no different from calling any other function. Throughout these lessons we have been using built-in functions like print(), type(), int(), str(), range(), etc. Our new function is no different except that we have defined it ourselves.

In [5]:
print('freezing point of water:', fahr_to_celsius(32), 'C')
print('boiling point of water:', fahr_to_celsius(212), 'C')
freezing point of water: 0.0 C
boiling point of water: 100.0 C

We’ve successfully called the function that we defined, and we have access to the value that we returned.

Composing Functions

Now that we’ve seen how to turn Fahrenheit into Celsius, we can also write the function to turn Celsius into Kelvin

In [6]:
def celsius_to_kelvin(temp_c):
    return temp_c + 273.15

We can run the function as we did before

In [7]:
print('freezing point of water in Kelvin:', celsius_to_kelvin(0.))
freezing point of water in Kelvin: 273.15

What about converting Fahrenheit to Kelvin? We could write out the formula from scratch, but we don’t need to. Instead, we can compose the two functions we have already created and test it:

In [8]:
def fahr_to_kelvin(temp_f):
    temp_c = fahr_to_celsius(temp_f)
    temp_k = celsius_to_kelvin(temp_c)
    return temp_k
In [9]:
print('boiling point of water in Kelvin:', fahr_to_kelvin(212.0))
boiling point of water in Kelvin: 373.15

This is our first taste of how larger programs are built: we define basic operations, then combine them in ever-large chunks to get the effect we want. Real-life functions will usually be larger than the ones shown here - typically half a dozen to a few dozen lines - but they shouldn’t ever be much longer than that. Otherwise the next person who reads it (likely to be our future selves!) won’t be able to understand what’s going on.

Defining Defaults

We have also already seen that it is possible to define default values. If you remember when we used range(10), 10 is the stop value of the function, but the function had default values, start = 0 and step = 1. We can do the same in our own functions and will consider the following example:

In [10]:
def display(a=1, b=2, c=3):
    print('a:', a, 'b:', b, 'c:', c)

We first run the function with no parameters:

In [11]:
print('no parameters:')
display()
no parameters:
a: 1 b: 2 c: 3

If we instead call the function with one, two or three values, these are used as the parameters of the function

In [12]:
print('one parameter:')
display(55)
print('two parameters:')
display(55, 66)
print('three parameters')
display(55,66,77)
one parameter:
a: 55 b: 2 c: 3
two parameters:
a: 55 b: 66 c: 3
three parameters
a: 55 b: 66 c: 77

This example shows that parameters are matched up from left to right, and any that haven’t been given a value explicitly get their default value.

We can override this behavior by assigning the value we pass in to a specific parameter in our function:

In [13]:
print('only setting the value of c')
display(c=77)
only setting the value of c
a: 1 b: 2 c: 77

Default values for optional arguments

As with range, which we have seen before, we can also make a selection of our function's parameters optional by assigning them default values. Consider the following function which evaluates the function $$ f(x) = \frac{x^2}{a^2} + \frac{y^2}{b^2} $$ with the default option of a=1.0, b=1.0.

In [14]:
def f(x, y, a=1.0, b=1.0):
    return x*x/(a*a) + y*y/(b*b)

Our function f has two required (or positional) arguments, x and y, and two optional arguments a and b. We can call our function f by supplying both the required arguments, and any number of the optional arguments.

In [15]:
#just supplying the required arguments
print(f(1.0,2.0)) 
#specifying the required arguments, and the optional arguments
#the optional arguments are assigned in the order they appear in the function definition
print(f(1.0,2.0,2.0,0.5)) 
#we can also specify the optional arguments by name
#you must match the name of the optional argument with that which appears in the function definition,
#if you use this method
print(f(1.0,2.0,a=2.0)) #a=2.0, b=1.0 (keeps default value)
print(f(1.0,2.0,b=0.5)) #a=1.0 (keeps default value), b=0.5
5.0
16.25
4.25
17.0

Readable Functions

As we suggested earlier it is best to write the functions so that they are readable so that we can more easily understand when we return to them in the future. Consider the following

In [16]:
def m(p):
    a = 0
    for v in p:
        a += v
    m = a / len(p)
    return m
In [17]:
def mean(sample):
    sample_sum = 0
    for value in sample:
        sample_sum += value

    sample_mean = sample_sum / len(sample)
    return sample_mean

The functions m and mean are computationally equivalent (they both calculate the sample mean), but to a human reader, they look very different. You probably found mean much easier to read and understand than m.

As this example illustrates, both documentation and a programmer’s coding style combine to determine how easy it is for others to read and understand the programmer’s code. Choosing meaningful variable names and using blank spaces to break the code into logical “chunks” are helpful techniques for producing readable code. This is useful not only for sharing code with others, but also for the original programmer. If you need to revisit code that you wrote months ago and haven’t thought about since then, you will appreciate the value of readable code!

Document your functions

You can also add documentation to your function using a docstring, to inform users what the code does and aid readability:

In [18]:
def mean(sample):
    '''
    Takes a list of numbers, sample,
    and returns the mean.
    '''
    sample_sum = 0
    for value in sample:
        sample_sum += value

    sample_mean = sample_sum / len(sample)
    return sample_mean

You can then get information about your function with help

In [19]:
help(mean)
Help on function mean in module __main__:

mean(sample)
    Takes a list of numbers, sample,
    and returns the mean.

Combining Strings

We have seen how we can “add” two strings to produce their concatenation: 'a' + 'b' is 'ab'. Write a function called fence that takes two parameters called original and wrapper and returns a new string that has the wrapper character at the beginning and end of the original. A call to your function should look like this:

print(fence('name', '*'))
*name*

Before you write your function think about what it will need to look like and write this out in natural language using pseudocode or a flow chart.

Solution

Return versus print

Note that return and print are not interchangeable. print is a Python function that prints data to the screen. It enables us, users, to see the data. The return statement, on the other hand, passes data back to the program that called the function. Let’s have a look at the following function:

def add(a, b):
    print(a + b)

If we run the follwing commands:

A = add(7, 3)
print(A)

what will be displayed?

Solution

Calling by name

Let's write a function that takes three parameters, year, month and day and prints them in a nice format:

In [23]:
def print_date(year, month, day):
    joined = str(year) + '/' + str(month) + '/' + str(day)
    print(joined)

Earlier 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. What does
    print_date(month=11, year=2018, day=21)
    
    print?
  3. When and why is it useful to call functions this way?

Solution

The Old Switcheroo

Consider this code:

a = 3
b = 7

def swap(a, b):
    temp = a
    a = b
    b = temp

swap(a, b)

print(a, b)

Which of the following would be printed if you were to run this code?

7 3
3 7
3 3
7 7

Why did you pick this answer?

Solution

Find the First

Now let's try combining all the components we have used so far. See if you can 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?

def first_negative(values):
    for v in ____:
        if ____:
            return ____

Once you have a function that you think will work, test it by creating a list of numbers and checking the result.

Solution

Key Points:

  • Define a function using def function_name(parameter).
  • The body of a function must be indented.
  • Call a function using function_name(value).
  • Variables defined within a function can only be seen and used within the body of the function.
  • If a variable is not defined within the function it is used, Python looks for a definition before the function call.
  • Use help(thing) to view help for something.
  • Put docstrings in functions to provide help for that function.
  • Specify default values for parameters when defining a function using name=value in the parameter list.
  • Parameters can be passed by matching based on name, by position, or by omitting them (in which case the default value is used).
  • Put code whose parameters change frequently in a function, then call it with different parameter values to customize its behavior.