Observera att detta är en arbetsversion av sidan!

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

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.

General appearance:
def name(p1, p2, p3...): s1 s2 s3 ...
where pi is a parameter name and si an arbitrary Python statement.

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 definitionComments
def fahrenheit_to_celsius(f):  
    return (5/9) * (f - 32)
  • The function is called fahrenheit_to_celsius. It is written in lowercase letters as according to the style guide. Underscores can be used to increase readability.
  • The function has one parameter f that is given within brackets.
  • Last comes a return statement. The statement contains an expression whose value is calculated and sent forward as the return value.

Example: Conversion from Fahrenheit to Kelvin

Function definitionComments
def fahrenheit_to_kelvin(f):
    c = fahrenheit_to_celsius(f)
    return c + 273.16
     
     
    
    
    
  • The function has one parameter f and one local variable c.
  • The function uses the previously-written function for conversion from Fahrenheit to Celsius.
  • The function has a local variable c which is given a value through a calculation.
  • As before the return statement contains an expression whose value is calculated and sent forward as the return value.
  • It isn't necessary to use a local variable as we've done here; we could have just written the entire expression
    fahrenheit_to_celsius(f) + 273.16
    in the return statement.
  • The variables f and c don't exist outside the function body.
Use examples
of conversion functions
PrintoutComments
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

  1. Write a function that takes a temperature in degrees of Celsius as input and returns the temperature in degrees of Fahrenheit. Answer
    def celsius_to_fahrenheit(c):
        return 9/5*c + 32
          
    
    As we can see here, it is possible to write the entire expression in the return statement, no local variable is needed.
  2. 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 and c have after the print statement.

    What conclusion can be drawn from this?

    Answer
    The variables f and c 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
Both functions have two local variables (sum and i). These exist only inside the function and their values are forgotten when the function is exited.

Exercises

  1. Write a function howmany(sum) that calculates and returns the number of terms necessary for the harmonic series to become larger than the given sum. Answer
    Most easily expressed with while:
    def howmany(sum):
        s = 0
        n = 0
        while s < sum:
            n += 1
            s += 1/n
        return n
    
  2. Write a function digits(x) that returns the number of digits in the given integer x (assume x is in base 10/is a decimal number). Answer
    def digits(x):
        n = 0
        while x != 0:
            n += 1
            x = x//10
        return n
    
    Bonus exercise: Solve this exercise using math.log!
  3. Write a function digitsum(x) as the sum of all the digits in the given integer x (assume x is in base 10/is a decimal number). Answer
    def digitsum(x):
        sum = 0
        while x != 0:
            sum += x % 10
            x = x//10
        return sum
    

Example: Function with multiple parameters

Function definitionComments
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 examplesValue
triangle_area(3, 4, 5) 6
triangle_area(1, 1, math.sqrt(2)) 0.49999999999999983

Exercises

  1. 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! Answer
    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 return None if the sides can't form a triangle. There are better ways to report errors, which will be brought up later in the course.
  2. Is there any other parameter values the code should watch out for? Answer
    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 definitionComment
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 exampleValue
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:

CodePrintout
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
Note:

Exercise

  1. Write a function is_twin_prime(n) that returns True if both n and n + 2 are prime numbers. Answer
    def is_twin_prime(n):
        return is_prime(n) and is_prime(n+2)
    

Example: squares that returns a list

def squares(a_list): res = [] for x in a_list: res.append(x*x) return res

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

def quad_equation(p, q): disc = p*p - 4*q if disc >= 0: d = math.sqrt(disc) x1 = (-p + d)/2.0 x2 = (-p - d)/2.0 return x1, x2 else: return None

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:

r = quad_equation(a, b) if r == None: print('Complex roots') else: print('x1: ', r[0]) print('x2: ', r[1])

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
If we now call the function with e.g. triangle_area(-1, 50, 5) the program will stop and exit with the following printout:
Traceback (most recent call last): File "test.py", line 14, in <module> print(triangle_area(-1, 50, 5)) File "test.py", line 8, in triangle_area raise ValueError('Illegal parameter values in triangle_area') ValueError: Illegal parameter values in triangle_area

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 tryexcept statement.

Example:
while True: print('Give side lengths') a = float(input('First: ')) b = float(input('Second: ')) c = float(input('Third: ')) try: print('The area is', triangle_area(a, b, c)) break except ValueError: print('\n*** Bad arguments to triangle_area!') print('Try again!')
Execution example:
Give side lengths First: 1 Second: 100 Third: 2 *** Bad arguments to triangle_area! Try again! Give side lengths First: -3 Second: 4 Third: 5 *** Bad arguments to triangle_area! Try again! Give side lengths First: 3 Second: 4 Third: 5 The area is 6.0

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

  1. 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

Valid CSS!