Lesson 4: Writing functions
Purpose: | Practice writing functions and algorithms |
Contents: | Function definitions, parameters, return values (singular and multiple) |
Work procedure: |
You are encouraged to discuss the work with others but should write your own code. It's best if you try to solve the solutions before looking at the answers! Remember to always read the answers closely, there is information there that isn't written anywhere else. Ask a teaching assistant if you're having trouble understanding something! |
Estimated working time: | 2 - 4 hours. |
Examination: | No mandatory examination. If you are having trouble understanding parts of the material or have other questions, feel free to grab a teaching assistant and ask them. |
Functions
In mathematics a function is defined as a relation between sets that associates to every element of a first set ("domain") exactly one element of the second set ("codomain"). The relation is unique in that one value from the domain always will give the same value from the codomain. Examples include the trigonometric functions sin, cos, ...
In the world of programming a broader interpretation of the concept "function" is used: they don't need to have a domain, nor a codomain, and they don't have to be unique.
Of the built-in functions you've (probably) seen are
-
abs
an example of a function that is unique, and has a domain as well as a codomain -
print
an example of a function without a codomain -
input
an example of a function without a domain and, furthermore, -
input
isn't unique - it can return different results for different calls with the same argument.
A function in the context of programming is a collection of instructions that carries out some (partial) task. Every function has a name that is used when we want those particular instructions to be executed. We can send information to a function via parameters, and the function can (but doesn't have to) return the results as the value of the function. This is more commonly referred to as the return value. Functions that don't return a result usually have some kind of side effect such as e.g. printing something to a terminal window or moving a turtle.
We said previously that functions don't necessarily return anything.
In Python, however, functions always returns a value.
If the function doesn't explicitly return a value it will automatically return the value None
.
See Wikipedia for a discussion on the concept of functions in the world of programming.
Python has a large number of predefined functions.
Some are always available (i.e. abs
, print
, input
, ...)
but most are in packages such as math
.
To use those functions we need to import said package.
In this chapter we will look at how to write and use our own functions.
Defining our own functions
The word def
prefaces the function,
followed by the function's name and eventual parameters within brackets (parentheses).
Multiple parameters are separate by commas.
Even if the function doesn't have any parameters, the bracket pair must still be there.
After this initial line follows the function body, that is,
the statements that shall be executed when the function is called.
The function body must be indented by 4 spaces.
A function body can have one or more return
statements.
When a return
statement is executed the program jumps back to
wherever the function was called.
If the return
statement contains an expression its value is calculated
and forwarded as a return value.
Functions with parameters and return values
Example: Conversion from Fahrenheit to Celsius
Function definition | Comments |
---|---|
def fahrenheit_to_celsius(f): return (5/9) * (f - 32) |
|
Example: Conversion from Fahrenheit to Kelvin
Function definition | Comments |
---|---|
def fahrenheit_to_kelvin(f): c = fahrenheit_to_celsius(f) return c + 273.16 |
|
Use examples of conversion functions | Printout | Comments |
---|---|---|
print(fahrenheit_to_celsius(50)) | 10.0 | The result is a float since the division operator / always gives
the result as a float.
|
print(fahrenheit_to_kelvin(32)) | 273.16 | |
x = 2 print(fahrenheit_to_celsius(x + 3)) | -15.0 |
In case the argument is an expression its value is calculated before being sent to the function.
Here, x + 3 is evaluates to 5 ,
ergo it is 5 that becomes the function's argument.
|
Exercises
-
Write a function that takes a temperature in degrees of Celsius as
input and returns the temperature in degrees of Fahrenheit.
def celsius_to_fahrenheit(c): return 9/5*c + 32
As we can see here, it is possible to write the entire expression in thereturn
statement, no local variable is needed. -
Copy and paste
def fahrenheit_to_celsius(f): c = (5/9) * (f - 32) return c print(fahrenheit_to_celsius(77))
Run the program and examine what values the variable
f
andc
have after theprint
statement.What conclusion can be drawn from this?
The variablesf
andc
do not exist outside the function body!This is very important! Parameters and local variables exist locally only.Thus, we can safely reuse these same names outside the function and in other functions.
Example: A function with multiple statements and local variables
Here are two version of a function that calculates the harmonic sum 1 + 1/2 + 1/3 + ... + 1/n
with while : |
with for : |
def harmonic(n): sum = 0 i = 1 while i <= n: sum += 1/i i += 1 return sum |
def harmonic(n): sum = 0 for i in range(1, n+1): sum += 1/i return sum |
sum
and i
).
These exist only inside the function and their values are forgotten when the function is exited.
Exercises
-
Write a function
howmany(sum)
that calculates and returns the number of terms necessary for the harmonic series to become larger than the givensum
.Most easily expressed withwhile
:def howmany(sum): s = 0 n = 0 while s < sum: n += 1 s += 1/n return n
-
Write a function
digits(x)
that returns the number of digits in the given integerx
(assume x is in base 10/is a decimal number).def digits(x): n = 0 while x != 0: n += 1 x = x//10 return n
Bonus exercise: Solve this exercise usingmath.log
! -
Write a function
digitsum(x)
as the sum of all the digits in the given integerx
(assume x is in base 10/is a decimal number).def digitsum(x): sum = 0 while x != 0: sum += x % 10 x = x//10 return sum
Example: Function with multiple parameters
Function definition | Comments |
---|---|
import math def triangle_area(a, b, c): s = (a + b + c)/2 t = s*(s-a)*(s-b)*(s-c) r = math.sqrt(t) return r |
Three parameters and three local variables |
Use examples | Value |
---|---|
triangle_area(3, 4, 5) | 6 |
triangle_area(1, 1, math.sqrt(2)) | 0.49999999999999983 |
Exercises
-
What happens if we call a function with values that can't be used to
form a triangle, e.g.
trianglearea(1,2,10)?
Modify the code to give an error printout if that happens!def triangle_area(a, b, c): s = (a + b + c)/2 t = s*(s-a)*(s-b)*(s-c) if t < 0: print("Can't form a triangle of: ", a, b, c) return None r = math.sqrt(t) return r
Here we've chosen to returnNone
if the sides can't form a triangle. There are better ways to report errors, which will be brought up later in the course. -
Is there any other parameter values the code should watch out for?
The function should check that all parameters are positive.
if a < 0 or b < 0 or c < 0: print('Illegal arguments to triangle_area:', a, b, c)
Example: Functions with parameters of type list
Function definition | Comment |
---|---|
def square_sum(a_list): result = 0 for x in a_list: result += x*x return result |
Remember how to iterate over the contents of a list! |
Use example | Value |
---|---|
square_sum([1, 2, 3, 4]) | 30 |
square_sum([23, 18, 57]) | 4102 |
square_sum([]) | 0 |
Functions without return values
Functions that don't return anything don't need a return
statement.
Such functions automatically return None
.
Example: Greeting function
def greet(name, course): print(f'Welcome to {course}, {name}!')
In this example the function takes in two parameters that are presumed to be strings. The function puts together a greeting message. As such, the function body only consists of one single statement.
Use example:
Code | Printout |
---|---|
greet('Ola','the world') greet('Anna','the world') |
Welcome to the world, Ola! Welcome to the world, Anna! |
course = 'Prog I' names = ['Eva', 'Gun', 'Åke'] for n in names: greet(n, course) |
Welcome to Prog I, Eva! Welcome to Prog I, Gun! Welcome to Prog I, Åke! |
k1 = "Prog1" k2 = "BV1" greet('Eva', k1 + ' och ' + k2) |
Welcome to Prog1 och BV1, Eva! |
Return values
Functions can return values of any type (int
, float
,
bool
, lists, strings, turtles, ...)
Example: A function that investigates whether a given numbers is a prime number
The function is_prime(n)
returns True
if n
is a prime number, that is, if it's only divisible by itself or 1.
A simple algorithm is to divide n by all numbers in the interval [2, n-1] and check if for any number the remainder is 0. If so n isn't a prime number.
It is, however, enough to check all numbers up to, and including, the square root of n. If n = p*q then both p and q can't both be larger than the square root of n.
def is_prime(n): limit = int(n**0.5) i = 2 for i in range(2, limit+1) if n%i == 0: return False return True
-
**0.5
instead ofmath.sqrt
-
Two
return
statements! -
As soon as we find a remainder that equals 0 we know that n can't be a prime number
and we can immediately exist the function with
False
as the return value. If we exit the loop no such remainder was found, ergo n is a prime number.
Exercise
-
Write a function
is_twin_prime(n)
that returnsTrue
if bothn
andn + 2
are prime numbers.def is_twin_prime(n): return is_prime(n) and is_prime(n+2)
Example: squares
that returns a list
squares([1, 2, 3, 4])
will return [1, 4, 9, 16]
Side note: In the lesson about lists we'll show an easier way of solving this problem.
Functions with multiple return values
A return
statement can contain multiple values separated by commas.
Example: A function that solves a quadratic equation x2 + px + q = 0 can be written like this (see exercise in Lesson 2):
Note: Two expressions separated by commas in the return
statement.
The return value becomes a tuple.
A tuple is roughly the same thing as a list, but with some limitations.
In order to read the values in a tuple we use the index operator [ ]
.
Example code | Printout | Comments |
---|---|---|
r = quad_equation(-3, 2) print(r) | (2.0, 1.0) | Result becomes a tuple |
x1, x2 = quad_equation(-3, 2) print(x1, x2) | 2.0 1.0 | The tuple is automatically unpacked. |
r = quad_equation(1, 1) print(r) | None |
Complex roots. The function exits without a set return value, so None is returned.
|
x1, x2 = quad_equation(1, 1) print(x1, x2) |
TypeError: cannot unpack non-iterable NoneType object |
Can't be done if a return statement with the values have been executed.
|
It's possible to test if the return value is None
:
Exceptions
We'll now discuss what a function should do if it receives an impossible task.
The function triangle_area
that was mentioned previously,
for example, should check that its received parameters
can actually be used to form a triangle.
The requirements to form a triangle are that the parameters are positive
and that the value that's sent to sqrt
is positive.
It is a lot easier to let the function throw an exception rather than print out an error message.
Example:def triangle_area(a, b, c): s = (a + b + c)/2 t = s*(s-a)*(s-b)*(s-c) if a <= 0 or b <= 0 or c <= 0 or t <= 0: raise ValueError('Illegal parameter values in triangle_area') r = math.sqrt(t) return r
triangle_area(-1, 50, 5)
the program will
stop and exit with the following printout:
If the given parameter values can't form a triangle the expression
raise ValueError(...)
creates a so called exception) of
the type ValueError
.
This is a predefined exception type that fits moderately well here.
(It would be even better to define our own exception type, but it is too early to bring that up here.)
Why is it better to do this than an error message printout with regular code? The printout may look even more cryptic and the program is terminated — is that actually what we wanted?
The problem is that the function triangle_area
certainly can find the problem, but it
has no idea how to rectify it. By throwing an exception it relinquishes the handling of the error
to whoever called the function. If the caller doesn't do anything the program is terminated, but it can
also choose to handle the error using a try
— except
statement.
If an exception of type ValueError
occurs in the indented block after
try
the block after except
will be executed, and thereafter
the statements in the while loop will be repeated. If no ValueError
occurs
the loop will terminate when the break
statement is executed.
The advantage here is that triangle_area
only reports the error
and lets the caller decide what to do about it.
There is more to say about exceptions, we'll come back to that later in the course.
Exercise
-
Assume that we don't actually want to allow complex roots
in the function
quad_equation
. Modify the code so that it throws an exception if it discovers such roots.
Go to next lesson or go back