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
-
absan example of a function that is unique, and has a domain as well as a codomain -
printan example of a function without a codomain -
inputan example of a function without a domain and, furthermore, -
inputisn'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 + 32As we can see here, it is possible to write the entire expression in thereturnstatement, 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
fandchave after theprintstatement.What conclusion can be drawn from this?
The variablesfandcdo 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 nBonus 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 rHere we've chosen to returnNoneif 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.5instead ofmath.sqrt -
Two
returnstatements! -
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
Falseas 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 returnsTrueif bothnandn + 2are 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