Observera att detta är en arbetsversion av sidan!

Lektion 4: Att skriva funktioner

Syfte: Öva på funktioner och algoritmer
Innehåll: Funktionsdefinitioner, parametrar, returvärden, multipla returvärden
Arbetsform:

Arbeta gärna tillsammans med någon men skriv egen kod. Diskutera med varandra!

Bäst är att försöka lösa uppgifterna innan du tittar på svaren! Läs dock alltid svaren noga - en del innehåller information som inte står någon annanstans! Fråga handledarna om det är något du inte förstår!

Uppskattad arbetstid: 2 - 4 timmar.
Redovisning: Ingen obligatorisk redovisning men diskutera med handledare om det är något du är osäker på!

Funktioner

I matematiken definiera man en funktion som en avbildning från en mängd, "definitionsmängden", till en annan mängd, "värdemängden". Exempel är bl.a., de trigonometriska funktionerna som sin, cos, ... Avbildningen ska vara entydig dvs samma värde från definitionsmängden ska alltid ge samma värde ur värdemängden.

I programmeringsvärlden använder man sig (i de flesta språken) av en vidare tolkning av begreppet funktion: de behöver inte ha någon definitionsmängd, de behöver inte ha någon värdemängd och de behöver inte heller vara entydiga.

Av de inbyggda funktioner du (troligen) sett är

En funktion i programmeringssammanhang är en samling instruktioner som utför någon viss (del-)uppgift. Funktioner har ett namn som man använder när man vill att dessa instruktioner ska utföras. Man kan skicka information till funktionen via parametrar och funktioner kan, men behöver inte, returnera resultat som funktionsvärde. Funktioner som inte returnera ett resultat har i regel någon sidoeffekt som t.ex. att skriva något i ett terminalfönster eller flytta på en padda.

Vi har ovan sagt att funktioner inte nödvändigtvis behöver returnera något värde. I Python är det dock så att funktioner alltid returnerar ett värde. Om man inte explicit returnerar ett värde så kommer funktionen returnera värdet None.

Se Wikipedia för en diskussion av funktionsbegreppet i programmeringsvärlden.

Python har ett stort antal fördefinierade funktioner. Några finns allmänt tillgängliga (t.ex. abs, print, input, ...) men de flesta finns i paket som t.ex. math.

Detta kapitel handlar om hur man skriver och använder egna funktioner.

Definiera egna funktioner

Ordet def inleder funktionsdefinitionen. Därefter följer funktionens namn och eventuella parametrar inom parentes. Parametrarna åtskiljs av kommatecken. Parentesparet måste finnas med även funktionen inte har några parametrar. Efter den inledande raden följer funktionskroppen dvs de satser som ska utföras när funktionen anropas. Funktionskroppen skrivs indenterat med 4 blanksteg. Funktionsdefinitionen är slut vid första oindenterade sats.

Allmänt utseende:
def namn(p1, p2, p3...): s1 s2 s3 ...
där pi är ett parameternamn och si en godtycklig Python-sats

I funktionskroppen kan en eller flera return-satser förekomma. När en sådan utförs återgår exekveringen till det ställe som anropade funktionen. Om return-satsen innehåller ett uttryck beräknas detta och värdet skickas tillbaka som funktionsvärde.

Funktioner med parametrar och returvärde

Exempel: Konvertering från Fahrenheit till Celsius

FunktionsdefinitionKommentarer
def fahrenheit_to_celsius(f): return (5/9) * (f - 32)
  • Funktionen heter fahrenheit_to_celsius. Enligt stilguiden ska namnet skrivas med gemena (små) bokstäver. Understrykningstecknet kan användas för att öka läsbarheten.
  • Funktionen har en parameter f som anges inom parenteser.
  • Sist kommer en return-sats. Satsen innehåller ett uttryck som beräknas och vars värde skickas tillbaka som funktionsvärde.

Exempel: Konvertering från Fahrenheit till Kelvin

FunktionsdefinitionKommentarer
def fahrenheit_to_kelvin(f): c = fahrenheit_to_celsius(f) return c + 273.16
  • Funktionen har en parameter f och en lokal variabel c.
  • Funktionen använder den ovan skrivna funktionen för att konvertera till grader Celsius.
  • Funktionen har en lokal variabel c. Den tilldelas sitt värde genom en beräkning.
  • Även här innehåller return-satsen ett uttryck som beräknas och vars värde returneras som funktionsvärde.
  • Det är inte alls nödvändigt att använda en lokal variabel — vi hade lika gärna kunnat skriva hela uttrycket
    fahrenheit_to_celsius(f) + 273.16
    i return-satsen.
  • Variablerna f och c existerar inte utanför funktionskroppen.
Exempel på användning
av konverteringsfunktionerna
UtskriftKommentar
print(fahrenheit_to_celsius(50)) 10.0 Resultatet blir av flyttalstyp eftersom divisionsoperatorn / alltid ger ett flyttal
print(fahrenheit_to_kelvin(32)) 273.16
x = 2 print(fahrenheit_to_celsius(x + 3))
-15.0
Argumentet dvs uttrycket x + 3 beräknas först till 5 som är det värde som skickas till parametern.

Övningar

  1. Skriv en funktion som tar ett temperatur uttryckt i grader Celsius och returnerar temperaturen i grader Fahrenheit. Svar
    def celsius_to_fahrenheit(c):
        return 9/5*c + 32
    		
    
    Det går alltså att skriva hela uttrycket på return-satsen. Ingen lokal variabel behövs då.
  2. Klistra in
    def fahrenheit_to_celsius(f): c = (5/9) * (f - 32) return c print(fahrenheit_to_celsius(77))

    Kör programmet och undersök vad variablerna f och c har för värde efter print-satsen!

    Vad kan man dra för slutsats av detta?

    Svar
    Variablerna fahr och celsius existerar inte utanför funktionskroppen!
    Detta är ett viktigt faktum! Parametrarna och lokala variabler finns bara lokalt.

    Man kan alltså utan risk för sammanblandning använda samma namn utanför funktionen och i andra funktioner.

Exempel: Funktion med flera satser och lokala variabler

En funktion för att beräkna den harmoniska summan  1 + 1/2 + 1/3 + ... + 1/n  i två varianter

     med while:      med 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
Båda funktionerna har två lokala variabler (sum och i). Dessa existerar bara inuti funktionen och deras värden "glöms" när funktionen lämnas.

Övningar

  1. Skriv en funktion howmany(sum) som beräknar och returnerar antalet termer som behövs i den harmoniska serien för att summan ska bli större än sum. Svar
    Enklast att uttrycka med while:
    def howmany(sum):
        s = 0
        n = 0
        while s < sum:
            n += 1
            s += 1/n
        return n
    
  2. Skriv en funktion digits(x) som returnerar antalet (decimala) siffror som finns i heltalet x. Låt digits(0) vara 0. Svar
    def digits(x):
        n = 0
        while x != 0:
            n += 1
            x = x//10
        return n
    

    Extra övning: Lös uppgiften med hjälp av math.log!

  3. Skriv en funktion digitsum(x) som siffersumman i heltalet x. Svar
    def digitsum(x):
        sum = 0
        while x != 0:
            sum += x % 10
            x = x//10
        return sum
    

Exempel: Funktion med flera parametrar

FunktionsdefinitionKommentarer
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 Tre parametrar och

tre lokala variabler
Exempel på användningVärde
triangle_area(3, 4, 5) 6
triangle_area(1, 1, math.sqrt(2)) 0.49999999999999983

Övningar

  1. Vad händer om man anropar funktionen med värden som inte kan utgöra sidor i samma triangel (t.ex. triangle_area(1, 2, 10))? Modifiera koden så att man får en bättre felutskrift! Svar
    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
    
    
    Här har vi valt att returnera None om sidorna inte kan formera en triangel. Det finns bättre sätt att rapportera fel men det kommer vi ta upp senare.
  2. Finns det några andra värden på parametrarna som koden bör se upp med? Svar
    Funktionen borde kontrollera att alla parametrarna är icke-negativa.
        if a < 0 or b < 0 or c < 0:
            print('Illegal arguments to triangle_area:', a, b, c)
    

Exempel: Funktion med parameter av listtyp

FunktionsdefinitionKommentar
def square_sum(a_list): result = 0 for x in a_list: result += x*x return result

Kom ihåg det smidiga sättet att iterera över innehållet i en lista!
Exempel på användningVärde
square_sum([1, 2, 3, 4]) 30
square_sum([23, 18, 57]) 4102
square_sum([]) 0

Funktioner utan returvärde

Funktioner som inte ska returnera något särskilt värde behöver inte ha någon return-sats. Sådana funktioner får ändå automatiskt returvärdet None.

Exempel: Hälsningsfunktion

def greet(name, course): print(f'Välkommen till {course}, {name}!')

I detta exempel får funktionen två parametrar som förutsätts vara av sträng-typ. Funktionen sätter ihop ett välkomstmeddelande. Funktionskroppen består alltså av en enda sats.

Exempel på användning:
KodUtskrift
greet('Ola','världen') greet('Anna','världen') Välkommen till världen, Ola! Välkommen till världen, Anna!
kurs = 'Prog I' namn = ['Eva', 'Gun', 'Åke'] for n in namn: greet(n, kurs) Välkommen till Prog I, Eva! Välkommen till Prog I, Gun! Välkommen till Prog I, Åke!
k1 = "Prog1" k2 = "BV1" greet('Eva', k1 + ' och ' + k2) Välkommen till Prog1 och BV1, Eva!

Returvärden

Funktioner kan returnera vilka typer av värden som helst (int, float, bool, listor, strängar, paddor, ...)

Exempel: En funktion som undersöker om ett givet tal är ett primtal

Funktionen is_prime(n) ska returnera True om n är ett primtal dvs bara delbart med sig själv eller 1.

En enkel algoritm är att kontrollera om någon rest vid division av n med alla tal som ligger i intervallet [2, n-1] är 0. I så fall är n inte ett primtal.

Det räcker dock att kontrollera talen upp till och med kvadratroten ur n (om n = p*q kan inte både p och q vara större än roten ur 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
Observera:

Övning

  1. Skriv funktionen is_twin_prime(n) som returnerar True om både n och n + 2 är primtal. Svar
    
    def is_twin_prime(n):
        return is_prime(n) and is_prime(n+2)
    

Exempel: squares som returnerar en lista

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

squares([1, 2, 3, 4])kommer att returnera [1, 4, 9, 16]

Anmärkning: I lektionen om listor kommer vi visa på ett enklare sätt att lösa detta problem.

Funktioner med flera returvärden

En return-sats kan innehålla flera värden åtskiljda av kommatecken.

Exempel En funktion som löser andragradsekvationen x2 + px + q = 0 kan skrivas så här (se övning i lektion 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 #

Observera: Två kommaseparerade uttryck på return-satsen.

Returvärdet bli då en tupel. En tupel är ungefär som en lista men men med en hel del begränsningar. För att avläsa värden i en tupel använde man index-operatorn [ ].

Exempelkod Utskrift Kommentar
r = quad_equation(-3, 2) print(r) (2.0, 1.0) Resultatet blir en tupel.
x1, x2 = quad_equation(-3, 2) print(x1, x2) 2.0 1.0 Tupeln packas automatiskt upp.  
r = quad_equation(1, 1) print(r) None Komplexa rötter. Funktionen slutar utan att något returvärde angivits. Värdet blir då satt till None.
x1, x2 = quad_equation(1, 1) print(x1, x2) TypeError: cannot unpack
non-iterable NoneType object
Går inte att göra om inte en returnsats med två värden utförts.

Man kan testa på om returvärdet är None:

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

Undantag

Vi ska nu diskutera vad en funktion bör göra om den får en omöjlig uppgift. Som redan påpekats så borde t.ex. funktionen triangle_area kontrollera att de tre längderna som den fick som parametrar faktiskt kan forma en triangel. För detta krävs dels att alla parametrarna är positiva och dels att värdet som skickas till sqrt är positivt.

Det är betydligt bättre att låta funktionen kasta ett undantag än att ge en felutskrift. Exempel:

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
Om vi nu anropar t.ex. med uttrycket triangle_area(-1, 50, 5) kommer programmet avbrytas med följande utskrift:
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
Om det parametervärdena inte kan forma en triangel skapar uttrycket raise ValueError(...) ett så kallat undantag (eng. exception) av typen ValueError. Detta är en fördefinierad undantagstyp som passar någorlunda bra här. (Det vore bättre att definiera en egen undantagstyp men det är för tidigt att ta upp det.)

Varför är då detta bättre än att göra en felutskrift med en code? Utskriften ser ju (kanske) mer kryptisk ut och programmet avbryts — ville vi verkligen det?

Problemet är att funktionen triangle_area visserligen kan upptäcka fel men den har ingen aning hur felet ska åtgärdas. Genom att kasta ett undantag överlåts den hanteringen till anroparen. Om den anropande koden inte gör något så avbryts programmet men den kan också välja att ta hand om felet med konstruktionen tryexcept. Exempel:

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!')
Exempel på körning:
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
Om ett undantag av typen ValueError inträffar i det indenterade blocket efter try så kommer blocket efter except utföras och därefter upprepas satserna i while-slingan. Om inget ValueError-undantag skapas så kommer slingan avbrytas när break-satsen utförs.

Fördelen är alltså att triangle_area bara rapporterar felet och låter anroparen avgöra vad som ska göras.

Det finns mer att säga om undantag men det återkommer vi till längre fram.

Övning

  1. Antag att vi verkligen inte vill tillåta komplexa rötter i funktionen quad_equation. Modifiera koden så att den kastar ett undantag om den upptäcker sådana.

Gå till nästa lektion eller gå tillbaka

Valid CSS!