Creating Functions
Overview
Teaching: 20 min
Exercises: 20 minQuestions
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.
Explain why we should divide programs into small, single-purpose functions.
As we’ve already said when looking at loops, reusing code is a very important part of good code
development and will save you a lot of pain in the long term! We’ve already seen how we can
use code that other people have written but what if we want to use our own code again,
on a different dataset or at a different point in our program?
Cutting and pasting it is going to make our code get very long and very repetitive,
very quickly.
We’d like a way to package our code so that it is easier to reuse,
and Python provides for this by letting us define our own ‘functions’ —
a shorthand way of re-executing longer pieces of code.
Let’s start by defining a function fahr_to_celsius
that converts temperatures
from Fahrenheit to Celsius:
def fahr_to_celsius(temp):
return ((temp - 32) * (5/9))
The function definition opens with the keyword def
followed by the
name of the function (fahr_to_celsius
) and a parenthesized list of parameter 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 a return
keyword followed by the return value.
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 our function.
fahr_to_celsius(32)
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:
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:
def celsius_to_kelvin(temp_c):
return temp_c + 273.15
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, but we don’t need to. Instead, we can compose the two functions we have already created:
def fahr_to_kelvin(temp_f):
temp_c = fahr_to_celsius(temp_f)
temp_k = celsius_to_kelvin(temp_c)
return temp_k
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, or the next person who reads it won’t be able to understand what’s going on.
Defining Defaults
We have seen how to pass arguments to a function. If you don’t pass these arguments, Python will complain:
print(fahr_to_kelvin())
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-11-d31ad434dbd6> in <module>()
----> 1 fahr_to_kelvin()
TypeError: fahr_to_kelvin() missing 1 required positional argument: 'temp_f'
However, there are many times where we may not want to give a particular argument or have the
function provide a default value. For example, let’s say we write an expanded print
function that
will prepend anything that is printed by a label or message if requested. The majority of the time
we want it to be a particular label but in some cases the code that calls the function
might want or need to change it. To avoid all calls to the
function having to remember to include the same label, we can set the default instead:
def label_print( msg, label_str = ">>> " ):
print(label_str, msg)
When calling this function we can now choose whether to leave out the defaulted argument or change it:
label_print("test1")
label_print("test2", "$$$ ")
>>> test1
$$$ test2
This is handy: if we usually want a function to work one way, but occasionally need it to do something else, we can allow people to pass a parameter when they need to but provide a default to make the normal case easier. The example below shows how Python matches values to parameters:
def display(a=1, b=2, c=3):
print('a:', a, 'b:', b, 'c:', c)
print('no parameters:')
display()
print('one parameter:')
display(55)
print('two parameters:')
display(55, 66)
no parameters:
a: 1 b: 2 c: 3
one parameter:
a: 55 b: 2 c: 3
two parameters:
a: 55 b: 66 c: 3
As this example shows, 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 naming the value as we pass it in:
print('only setting the value of c')
display(c=77)
only setting the value of c
a: 1 b: 2 c: 77
Readable functions
Consider these two functions:
def s(p):
a = 0
for v in p:
a += v
m = a / len(p)
d = 0
for v in p:
d += (v - m) * (v - m)
return numpy.sqrt(d / (len(p) - 1))
def std_dev(sample):
sample_sum = 0
for value in sample:
sample_sum += value
sample_mean = sample_sum / len(sample)
sum_squared_devs = 0
for value in sample:
sum_squared_devs += (value - sample_mean) * (value - sample_mean)
return numpy.sqrt(sum_squared_devs / (len(sample) - 1))
The functions s
and std_dev
are computationally equivalent (they
both calculate the sample standard deviation), but to a human reader,
they look very different. You probably found std_dev
much easier to
read and understand than s
.
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!
Combining Strings
“Adding” two strings produces their concatenation:
'a' + 'b'
is'ab'
. Write a function calledfence
that takes two parameters calledoriginal
andwrapper
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*
Solution
def fence(original, wrapper): return wrapper + original + wrapper
Return versus print
Note that
return
andreturn
statement, on the other hand, makes data visible to the program. Let’s have a look at the following function:def add(a, b): print(a + b)
Question: What will we see if we execute the following commands?
A = add(7, 3) print(A)
Solution
Python will first execute the function
add
witha = 7
andb = 3
, and, therefore, print10
. However, because functionadd
does not have a line that starts withreturn
(noreturn
“statement”), it will, by default, return nothing which, in Python world, is calledNone
. Therefore,A
will be assigned toNone
and the last line (print(A)
) will printNone
. As a result, we will see:10 None
Selecting Characters From Strings
If the variable
s
refers to a string, thens[0]
is the string’s first character ands[-1]
is its last. Write a function calledouter
that returns a string made up of just the first and last characters of its input. A call to your function should look like this:print(outer('helium'))
hm
Solution
def outer(input_string): return input_string[0] + input_string[-1]
Variables Inside and Outside Functions
What does the following piece of code display when run — and why?
f = 0 k = 0 def f2k(f): k = ((f-32)*(5.0/9.0)) + 273.15 print(k) return k f2k(8) f2k(41) f2k(32) print(k)
Solution
259.81666666666666 287.15 273.15 0
k
is 0 because thek
inside the functionf2k
doesn’t know about thek
defined outside the function.
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? Why did you pick this answer?
7 3
3 7
3 3
7 7
Solution
3, 7
is correct. Initiallya
has a value of 3 andb
has a value of 7. When the swap function is called, it creates local variables (also calleda
andb
in this case) and trades their values. The function does not return any values and does not altera
orb
outside of its local copy. Therefore the original values ofa
andb
remain unchanged.
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
Put code whose parameters change frequently in a function, then call it with different parameter values to customize its behavior.