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: 4 timmar.
Redovisning: Ingen obligatorisk redovisning men diskutera med handledare om det är något du är osäker på!
Funktioner#
I matematiken definieras 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
ett exempel på en funktion som har både värdemängd och definitionsmängd och dessutom är entydig,print
ett exempel på en funktion utan värdemängd, ochinput
ett exempel på en funktion som inte är 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 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 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 om 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 p1, p2, … är parameternamn och s1, s2, … är godtyckliga Python-satser.
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 |
print(fahrenheit_to_kelvin(32))
|
273.16 |
|
x = 2
print(fahrenheit_to_celsius(x + 3))
|
-15.0 |
Argumentet, dvs. uttrycket |
Övningar#
Skriv en funktion som tar en temperatur 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 i 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?
Svar
Variablerna
f
ochc
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#
Skriv en funktion
how_many(limit)
som beräknar och returnerar antalet termer som behövs i den harmoniska serien för att summan ska bli större änlimit
.Svar
Enklast att uttrycka med
while
:def how_many(limit): sum = 0 n = 0 while sum < limit: n += 1 sum += 1/n return n
Skriv en funktion
digits(x)
som returnerar antalet siffror som finns i det positiva heltaletx
. Låtdigits(0)
vara 0. Tips: Använd heltalsdivision (//
).Svar
def digits(x): n = 0 while x != 0: n += 1 x = x//10 return n
Lös uppgiften ovan med hjälp av
math.log10
istället.Svar
import math def digits(x): if x == 0: # Specialhantera fallet x=0 eftersom log10(0) är odefinierat. return 0 else: return int(math.log10(x)) + 1
Skriv en funktion
digit_sum(x)
som returnerar summan av siffrorna heltaletx
. Tips: Använd modulo-operatorn (%
) som ger resten vid heltalsdivision.Svar
def digit_sum(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)
)? Om den lokala variabelnt
får ett negativt värde så innebär detta att de inmatade sidorna inte kan utgöra en triangel. Modifiera funktionen så att man får en bättre felutskrift i detta fall!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.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#
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å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:
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 \(\sqrt{n}\) (om \(n = pq\) kan inte både \(p\) och \(q\) vara större än \(\sqrt{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:
**0.5
ger roten ur (det hade också gått att använda `math.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. Kom ihåg att funktionen lämnas så fort enreturn
-sats exekveras. Om man kommer ur loopen så hittades ingen division som gick jämnt ut - alltså ärn
ett primtal och funktionen ska returneraTrue
.
Övning#
Skriv funktionen
is_twin_prime(n)
som returnerarTrue
om båden
ochn + 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 \(x^2 + px + q = 0\) kan skrivas så här (se övning i lektion 2):
def quad_equation(p, q):
discriminant = p*p - 4*q
if discriminant >= 0:
d = math.sqrt(discriminant)
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(1, 1)
print(r)
|
None
|
Komplexa rötter. Funktionen returnerar i detta fall |
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 |
Man kan undersöka om returvärdet är None innan något skrivs ut:
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 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 tar vi inte upp här.)
Varför är då detta bättre än att göra en felutskrift med print
?
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:
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
-loopen. Om inget
ValueError
-undantag skapas så kommer loopen 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.
Ö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.