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
-
abs
exempel på en funktion som har både värdemängd och definitionsmängd och dessutom är entydig, -
print
exempel på funktion utan värdemängd, -
input
exempel på funktion utan definitionsmängd och -
input
är inte heller entydig - den kan ju ge olika resultat vid olika anrop med samma argument.
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.
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
Funktionsdefinition | Kommentarer |
---|---|
def fahrenheit_to_celsius(f): return (5/9) * (f - 32) |
|
Exempel: Konvertering från Fahrenheit till Kelvin
Funktionsdefinition | Kommentarer |
---|---|
def fahrenheit_to_kelvin(f): c = fahrenheit_to_celsius(f) return c + 273.16 |
|
Exempel på användning av konverteringsfunktionerna | Utskrift | Kommentar |
---|---|---|
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
-
Skriv en funktion som tar ett temperatur uttryckt i grader Celsius och returnerar
temperaturen i grader Fahrenheit.
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å. -
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
ochc
har för värde efterprint
-satsen!Vad kan man dra för slutsats av detta?
Variablernafahr
ochcelsius
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
|
sum
och i
).
Dessa existerar bara inuti funktionen och deras värden "glöms" när funktionen lämnas.
Övningar
-
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 änsum
.Enklast att uttrycka medwhile
:def howmany(sum): s = 0 n = 0 while s < sum: n += 1 s += 1/n return n
-
Skriv en funktion
digits(x)
som returnerar antalet (decimala) siffror som finns i heltaletx
. Låtdigits(0)
vara 0.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
! -
Skriv en funktion
digitsum(x)
som siffersumman i heltaletx
.def digitsum(x): sum = 0 while x != 0: sum += x % 10 x = x//10 return sum
Exempel: Funktion med flera parametrar
Funktionsdefinition | Kommentarer |
---|---|
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ändning | Värde |
---|---|
triangle_area(3, 4, 5) | 6 |
triangle_area(1, 1, math.sqrt(2)) | 0.49999999999999983 |
Övningar
-
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!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 returneraNone
om sidorna inte kan formera en triangel. Det finns bättre sätt att rapportera fel men det kommer vi ta upp senare. -
Finns det några andra värden på parametrarna som koden bör se upp med?
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
Funktionsdefinition | Kommentar |
---|---|
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ändning | Vä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ågonreturn
-sats.
Sådana funktioner får ändå automatiskt returvärdet None
.
Exempel: Hälsningsfunktion
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:Kod | Utskrift |
---|---|
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).
-
**0.5
i stället förmath.sqrt
-
Två
return
-satser!Så fort man hittar en rest som är 0 vet man att det inte kan vara ett primtal och kan direkt lämna funktionen med
False
som värde. Om man kommer ur loopen så hittades ingen division som gick jämnt ut — alltså primtal.
Övning
-
Skriv funktionen
is_twin_prime(n)
som returnerarTrue
om båden
ochn + 2
är primtal.def is_twin_prime(n): return is_prime(n) and is_prime(n+2)
Exempel: squares
som returnerar en lista
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):
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 return sats med två värden utförts.
|
Man kan testa på om returvärdet är None
:
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. funktionentriangle_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:
triangle_area(-1, 50, 5)
kommer programmet avbrytas med följande utskrift:
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 try
— except
.
Exempel:
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
-
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