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!

Tips: Använd debuggern

Uppskattad arbetstid: Schemalagd handledningstid: 4 timmar. Utöver det räkna med eget arbete med lika många timmar.
Redovisning: Ingen obligatorisk redovisning men diskutera med handledare om det är något du är osäker på!
Backup: När du arbetar på egen dator måste du se till att det löpande görs backup på de filer (py-filer) som du skapar under kursens gång. Om du har filerna enbart lagrade på din dator finns risk att allt försvinner vid ett datorhaveri eller vid stöld.

Funktioner

I matematiken definierar 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 returnerar ett resultat har i regel någon sidoeffekt som t.ex. att skriva något i ett fö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.

Denna lektion handlar om hur man skriver och använder egna funktioner.

Definiera egna funktioner

En funktionsdefinition består av två delar:

Funktionsdefinitionen är slut vid första oindenterade sats.

Allmänt utseende:
def namn(p1, p2, p3...): # Huvud s1 # Kroppen s2 # Kroppen s3 # Kroppen ... # Kroppen
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

För att få lite ordning och reda kan det vara bra att du börjar med att skapa en mapp för de filer (funktioner/program) du kommer skapa i denna lektion, dvs enligt samma princip du gjorde i lektion 2 och 3.

Skriv koden till de olika funktionsdefinitionerna i samma pythonfil. Du kommer därmed till slut att få flera funktioner som finns i samma fil. Namnge filen med lämpligt namn, kanske funktioner.

Exempel 1: 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.
Kopiera koden (funktionsdefinitionen ovan) till din tomma pythonfil. Om du sedan kör (run) filen kommer inget att hända, inget resultat skrivs ut, eftersom det ej sker något anrop av funktionen och utskrift av resultat. Detta kan göras exvis på följande sätt, testa alla sätten:

Exempel 2: Konvertering från Fahrenheit till Kelvin

Denna funktion anropar i sin tur funktionen i exempel 1.
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.
Kopiera koden (funktionsdefinitionen ovan) till din pythonfil, där funktionen fahrenheit_to_celsius finns och anropet till den. Gör anrop av funktionen fahrenheit_to_kelvin på samma olika sätt som du tidigare gjorde med funktionen fahrenheit_to_celsius. Tips: Kommentera bort anropen av fahrenheit_to_celsius i filen, så slipper du dessa utskrifter.
Några exempel på användning (anrop)
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 en temperatur uttryckt i grader Celsius (som parameter) och returnerar temperaturen i grader Fahrenheit. Lösningsförslag
    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!
    Tips: Hur undersöka variablers värden? Jo, skriv ut dem.

    Vad kan man dra för slutsats av detta?

    Lösningsförslag
    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

Mest lärorikt är att först skriva sin egen kod som löser problemet. Därefter jämföra med lösningsförslaget.
  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. Lösningsförslag
    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. Lösningsförslag
    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. Lösningsförslag
    def digitsum(x):
        sum = 0
        while x != 0:
            sum += x % 10
            x = x//10
        return sum
    

Exempel: Funktion med flera parametrar

FunktionsdefinitionKommentarer
import math # Herons formel: Beräknar arean av # en triangel med sidorna a, b, c 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! Lösningsförslag
    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? Lösningsförslag
    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
>>>a_list = [1, 2, 3, 4] >>>square_sum(a_list) 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. Lösningsförslag
    
    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 med en hel del begränsningar. För att avläsa värden i en tupel använder 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(-3, 2) print(r[0], r[1]) 2.0 1.0 Indexering i tupeln r  
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