💻 Kontrollflyt og funksjoner#
Oversikt#
I forrige notebook (“En smakebit av Python”) så vi på variabler og datatyper. Den siste kodecellen der regnet ut midtpunktet av fire byer med denne linjen:
centroid_lengdegrad = sum([punkt[0] for punkt in punkter]) / len(punkter)
Den linjen inneholder tre ting vi ikke har forklart ennå: en løkke, en måte å
bygge opp et resultat på, og funksjonene sum() og len(). I denne
forelesningen tetter vi det hullet. Vi ser på:
Sammenligninger og boolske uttrykk – hvordan Python svarer
True/FalseBetingelser –
if,elif,else: kjøre kode bare når noe stemmerLøkker –
for: gjøre det samme for hvert element i en samlingFunksjoner –
def: pakke kode i noe du kan gi navn, gjenbruke og testeimport– hente inn kode som andre har skrevet
Dette er byggeklossene resten av kurset hviler på. Allerede neste uke skal du
skrive funksjoner med assert (se forelesningen om defensive programming), og
fra uke 3 leser vi data i løkker og skriver egne funksjoner for å behandle
geometrier.
1. Sammenligninger og boolske uttrykk#
Før vi kan be Python om å gjøre noe “bare hvis …”, trenger vi uttrykk som
svarer True eller False. Det får vi fra sammenligningsoperatorene:
Operator |
Betydning |
|---|---|
|
er lik |
|
er ikke lik |
|
mindre enn, større enn |
|
mindre/større enn eller lik |
Warning
Ett likhetstegn (=) tilordner en verdi til en variabel. To likhetstegn
(==) sammenligner. Dette er en av de vanligste skrivefeilene i Python.
lengdegrad = 10.776
print(lengdegrad > 0) # er punktet øst for nullmeridianen?
print(lengdegrad == 10.776) # er verdien nøyaktig 10.776?
print(lengdegrad != 5.0)
True
True
True
Resultatet av en sammenligning er en bool. Vi kan lagre den i en variabel:
er_ost_for_null = lengdegrad > 0
print(er_ost_for_null)
print(type(er_ost_for_null))
True
<class 'bool'>
Kombinere flere betingelser: and, or, not#
and– sann bare hvis begge sidene er sanneor– sann hvis minst én side er sannnot– snurTruetilFalseog omvendt
Fastlands-Norge ligger omtrent mellom 4° og 31° østlig lengde og mellom 58° og 71° nordlig bredde. Vi kan sjekke om et punkt er innenfor denne “boksen”:
lengdegrad = 10.776
breddegrad = 59.665
innenfor_norge = (4 < lengdegrad < 31) and (58 < breddegrad < 71)
print(innenfor_norge)
True
Legg merke til 4 < lengdegrad < 31. I Python kan du lenke sammenligninger – det leses som “4 er mindre enn lengdegrad, som er mindre enn 31”.
in: er noe med i en samling?#
in sjekker om en verdi finnes i en liste (eller andre samlinger), og gir også
en bool:
stottede_epsg = [25832, 25833, 25835]
print(25833 in stottede_epsg)
print(4326 in stottede_epsg)
print(4326 not in stottede_epsg)
True
False
True
2. Betingelser: if, elif, else#
Med boolske uttrykk på plass kan vi la programmet velge hva det skal gjøre.
Grunnformen:
if <betingelse>:
# denne blokken kjører bare hvis betingelsen er True
...
Alt som er rykket inn (vanligvis 4 mellomrom) under if-linjen hører til
betingelsen. Innrykk er ikke bare pynt i Python – det er slik språket vet hva
som er “inni” if-en.
breddegrad = 69.65 # Tromsø
if breddegrad > 66.5:
print("Dette punktet ligger nord for polarsirkelen.")
Dette punktet ligger nord for polarsirkelen.
else: hva ellers?#
else fanger opp alle tilfellene der if-betingelsen var False:
breddegrad = 59.91 # Oslo
if breddegrad > 66.5:
print("Nord for polarsirkelen.")
else:
print("Sør for polarsirkelen.")
Sør for polarsirkelen.
elif: flere alternativer#
elif (kort for “else if”) lar deg teste flere betingelser etter hverandre.
Python går gjennom dem ovenfra og ned og kjører den første som er sann –
resten hoppes over.
Et GIS-eksempel: hvilken UTM-sone (og dermed hvilket EPSG-nummer for ETRS89) bør vi bruke for et punkt i Norge, basert på lengdegraden?
lengdegrad = 10.776
if lengdegrad < 6:
epsg = 25831 # UTM-sone 31
elif lengdegrad < 12:
epsg = 25832 # UTM-sone 32
elif lengdegrad < 18:
epsg = 25833 # UTM-sone 33
else:
epsg = 25835 # UTM-sone 35 (Øst-Finnmark)
print("Bruk EPSG:", epsg)
Bruk EPSG: 25832
Prøv å endre lengdegrad til 15.2 eller 4.1 og kjør cellen på nytt.
Note
Rekkefølgen betyr noe. Da lengdegrad = 10.776, er både < 12 og < 18
sanne, men Python stopper ved det første treffet (elif lengdegrad < 12).
Delvis utfylt eksempel#
Cellen under klassifiserer et areal (i kvadratmeter) i “lite”, “middels” eller
“stort”. To av grenene er fylt ut – fyll inn resten der det står ___.
areal_m2 = 45000
if areal_m2 < 10000:
kategori = "lite"
elif areal_m2 < 100000:
kategori = ___
else:
kategori = ___
print(f"Et areal på {areal_m2} m2 er {kategori}.")
Løsningsforslag
if areal_m2 < 10000:
kategori = "lite"
elif areal_m2 < 100000:
kategori = "middels"
else:
kategori = "stort"
Sjekk din forståelse
Skriv en if/elif/else som setter variabelen retning til "nord",
"sør" eller "ekvator" ut fra en breddegrad bredde:
positiv breddegrad →
"nord"negativ breddegrad →
"sør"nøyaktig 0 →
"ekvator"
bredde = 59.91
# Din kode her:
Løsningsforslag
bredde = 59.91
if bredde > 0:
retning = "nord"
elif bredde < 0:
retning = "sør"
else:
retning = "ekvator"
print(retning)
3. Løkker: for#
En for-løkke gjør det samme for hvert element i en samling – for eksempel
hver rad i et datasett, hver fil i en mappe, eller hvert koordinatpar i en
liste.
for <element> in <samling>:
# denne blokken kjører én gang per element
...
byer = ["Oslo", "Bergen", "Trondheim", "Tromsø"]
for by in byer:
print("Behandler:", by)
Behandler: Oslo
Behandler: Bergen
Behandler: Trondheim
Behandler: Tromsø
Variabelen by er ikke noe spesielt – den peker bare på ett element om gangen,
og navnet velger du selv. Etter løkka er by lik det siste elementet.
range(): løkke et bestemt antall ganger#
range(n) gir tallene 0, 1, ..., n-1:
for i in range(4):
print(i)
0
1
2
3
enumerate(): både posisjon og element#
Ofte vil du ha både radnummeret og verdien. enumerate() gir deg begge:
byer = ["Oslo", "Bergen", "Trondheim", "Tromsø"]
for nr, by in enumerate(byer):
print(nr, by)
0 Oslo
1 Bergen
2 Trondheim
3 Tromsø
Løkke over en ordbok (dict)#
.items() gir deg nøkkel og verdi samtidig:
attributter = {
"navn": "Ås",
"befolkning": 10962,
"type": "tettsted",
}
for nokkel, verdi in attributter.items():
print(f"{nokkel}: {verdi}")
navn: Ås
befolkning: 10962
type: tettsted
Bygge opp et resultat: “akkumulator-mønsteret”#
Dette er mønsteret som gjemte seg i sum([punkt[0] for punkt in punkter]) fra
forrige notebook. Ideen: start med et tomt resultat (ofte 0 eller en tom
liste), og oppdater det inni løkka.
Her regner vi ut gjennomsnittlig lengdegrad for de fire byene – akkurat samme tall som listekomprehensjonen ga:
punkter = [
[10.738, 59.913], # Oslo
[5.323333, 60.3925], # Bergen
[10.4, 63.44], # Trondheim
[18.96366, 69.65455], # Tromsø
]
sum_lengde = 0
for punkt in punkter:
sum_lengde += punkt[0] # legg til lengdegraden fra dette punktet
snitt_lengde = sum_lengde / len(punkter)
print("Gjennomsnittlig lengdegrad:", snitt_lengde)
Gjennomsnittlig lengdegrad: 11.35624825
Sammenlign med den korte varianten fra forrige forelesning:
snitt_lengde = sum([punkt[0] for punkt in punkter]) / len(punkter)
Uttrykket [punkt[0] for punkt in punkter] kalles en listekomprehensjon. Det
er en kortform for “lag en ny liste ved å løkke over punkter og plukke ut
punkt[0] fra hver”. Du trenger ikke skrive slike selv ennå – men nå vet du hva
de betyr når du ser dem.
Løkke + betingelse: filtrere#
Kombinerer du for og if, kan du plukke ut bare de elementene du vil ha. Her
teller vi hvor mange byer som ligger nord for polarsirkelen:
byer = [
{"navn": "Oslo", "bredde": 59.913},
{"navn": "Bergen", "bredde": 60.3925},
{"navn": "Trondheim", "bredde": 63.44},
{"navn": "Tromsø", "bredde": 69.65455},
]
antall_nord = 0
for by in byer:
if by["bredde"] > 66.5:
antall_nord += 1
print(by["navn"], "ligger nord for polarsirkelen")
print("Totalt:", antall_nord)
Tromsø ligger nord for polarsirkelen
Totalt: 1
Delvis utfylt eksempel#
Cellen under skal lage en ny liste store_byer med navnene på byene som har
mer enn 100 000 innbyggere. Fyll inn der det står ___.
byer = [
{"navn": "Oslo", "befolkning": 709037},
{"navn": "Bergen", "befolkning": 291940},
{"navn": "Ås", "befolkning": 10962},
{"navn": "Tromsø", "befolkning": 77544},
]
store_byer = []
for by in byer:
if ___ > 100000:
store_byer.___(by["navn"])
print(store_byer)
Løsningsforslag
store_byer = []
for by in byer:
if by["befolkning"] > 100000:
store_byer.append(by["navn"])
print(store_byer) # ['Oslo', 'Bergen']
Sjekk din forståelse
Bruk punkter-listen fra tidligere (lengdegrad, breddegrad for fire byer).
Skriv en løkke som regner ut gjennomsnittlig breddegrad (punkt[1]), på
samme måte som vi gjorde for lengdegrad.
punkter = [
[10.738, 59.913],
[5.323333, 60.3925],
[10.4, 63.44],
[18.96366, 69.65455],
]
# Din kode her:
Løsningsforslag
sum_bredde = 0
for punkt in punkter:
sum_bredde += punkt[1]
snitt_bredde = sum_bredde / len(punkter)
print(snitt_bredde) # 63.3500125
4. Funksjoner: def#
En funksjon er en bit kode du gir et navn, slik at du kan kjøre den igjen og
igjen uten å skrive den på nytt. Du har allerede brukt mange: print(),
len(), sum(), range(), math.radians().
Hvorfor skrive egne?
Gjenbruk – skriv logikken én gang, bruk den mange steder
Navngiving –
utm_sone_epsg(lengdegrad)sier hva koden gjør; ti linjer medif/elifgjør det ikkeTesting – en funksjon med tydelig inn- og utdata er lett å sjekke. Neste uke legger vi til
asserti funksjoner akkurat som disse.
Grunnformen:
def funksjonsnavn(parameter1, parameter2):
"""Kort forklaring av hva funksjonen gjør."""
...
return resultat
def polarsirkelen(breddegrad):
"""Returnerer True hvis breddegraden er nord for polarsirkelen (66,5 grader N)."""
return breddegrad > 66.5
print(polarsirkelen(69.65)) # Tromsø -> True
print(polarsirkelen(59.91)) # Oslo -> False
True
False
Noen begreper:
defstarter definisjonen. Å definere funksjonen kjører den ikke – det skjer først når du kaller den, sompolarsirkelen(69.65).Parametere (
breddegrad) er navnene funksjonen bruker på det den får inn.Docstring – teksten i trippel-hermetegn rett under
def-linjen. Den forklarer funksjonen for den neste som leser koden (ofte deg selv om tre uker).returnsender en verdi tilbake til den som kalte funksjonen. Utenreturngir funksjonenNone.
En funksjon med if/elif#
La oss pakke UTM-sone-logikken fra del 2 inn i en funksjon:
def utm_sone_epsg(lengdegrad):
"""Returnerer EPSG-koden for riktig ETRS89 / UTM-sone i Norge."""
if lengdegrad < 6:
return 25831
elif lengdegrad < 12:
return 25832
elif lengdegrad < 18:
return 25833
else:
return 25835
print(utm_sone_epsg(10.776)) # -> 25832
print(utm_sone_epsg(15.2)) # -> 25833
25832
25833
Legg merke til at vi kan bruke return flere ganger. Så snart Python treffer en
return, hopper det ut av funksjonen.
Standardverdier for parametere#
Du kan gi en parameter en standardverdi, som brukes hvis den som kaller funksjonen ikke oppgir noe:
def nord_for(breddegrad, grense=66.5):
"""True hvis punktet ligger nord for en gitt breddegrad (standard: polarsirkelen)."""
return breddegrad > grense
print(nord_for(64.0)) # bruker grense=66.5 -> False
print(nord_for(64.0, grense=63)) # egen grense -> True
False
True
Returnere flere verdier#
En funksjon kan returnere flere verdier på én gang (teknisk sett en tuple).
Her samler vi opp midtpunkt-koden fra del 3 i én gjenbrukbar funksjon:
def midtpunkt(punkter):
"""Gjennomsnittlig lengde- og breddegrad for en liste med [lengde, bredde]-par."""
sum_lengde = 0
sum_bredde = 0
for punkt in punkter:
sum_lengde += punkt[0]
sum_bredde += punkt[1]
snitt_lengde = sum_lengde / len(punkter)
snitt_bredde = sum_bredde / len(punkter)
return snitt_lengde, snitt_bredde
punkter = [
[10.738, 59.913],
[5.323333, 60.3925],
[10.4, 63.44],
[18.96366, 69.65455],
]
x, y = midtpunkt(punkter)
print(f"Midtpunkt: {x:.4f}, {y:.4f}")
Midtpunkt: 11.3562, 63.3500
Note
Variabler som lages inne i en funksjon (sum_lengde, snitt_bredde) finnes
bare der. Prøv å skrive print(sum_lengde) i en egen celle etterpå – du får
en NameError. Funksjonen kommuniserer med omverdenen gjennom parameterne den
tar inn og verdien den returnerer.
Delvis utfylt eksempel#
Fullfør funksjonen tell_nord, som skal telle hvor mange punkter i en liste som
ligger nord for polarsirkelen. Bruk nord_for-funksjonen fra tidligere.
def tell_nord(punkter):
"""Teller hvor mange [lengde, bredde]-punkter som ligger nord for polarsirkelen."""
antall = 0
for punkt in punkter:
if ___:
antall ___ 1
return antall
print(tell_nord(punkter)) # forventet: 1
Løsningsforslag
def tell_nord(punkter):
antall = 0
for punkt in punkter:
if nord_for(punkt[1]):
antall += 1
return antall
Sjekk din forståelse
Skriv en funksjon km_til_m(distanse_km) som gjør om en distanse i kilometer
til meter, med en docstring. Test den med km_til_m(4.2) (skal gi 4200.0).
(Neste uke skal du legge til en assert som sier fra hvis noen sender inn et
negativt tall.)
# Din kode her:
Løsningsforslag
def km_til_m(distanse_km):
"""Gjør om en distanse fra kilometer til meter."""
return distanse_km * 1000
print(km_til_m(4.2)) # 4200.0
5. import: bruke kode andre har skrevet#
Python kommer med et stort standardbibliotek, og i dette kurset installerer
vi i tillegg pakker som geopandas, shapely og matplotlib. import er
hvordan du henter dem inn.
Du så allerede import math i forrige forelesning:
import math
print(math.sqrt(144))
print(math.pi)
12.0
3.141592653589793
Tre måter å importere på#
import math # hele modulen – bruk som math.sqrt(...)
from math import sqrt # bare én ting – bruk som sqrt(...)
import geopandas as gpd # gi modulen et kortnavn – bruk som gpd.read_file(...)
from math import hypot
# rett-linje-avstand mellom to punkter (i et plant koordinatsystem, meter)
dx = 599200 - 601000
dy = 6643000 - 6651000
print(hypot(dx, dy), "meter")
8200.0 meter
Konvensjoner du vil se hele kurset#
Noen pakker importeres nesten alltid med et fast kortnavn. Følg samme konvensjon, så er koden din lett å lese for andre:
import pandas as pd
import geopandas as gpd
import numpy as np
import matplotlib.pyplot as plt
En forsmak på neste uke#
shapely (uke 2) er allerede installert i kursmiljøet. Slik henter du inn
Point-klassen og lager et punkt:
from shapely.geometry import Point
oslo = Point(10.738, 59.913)
print(oslo)
print(type(oslo))
POINT (10.738 59.913)
<class 'shapely.geometry.point.Point'>
Warning
Hvis en import gir ModuleNotFoundError, er som regel pakken ikke installert i
miljøet du kjører – eller du kjører feil kjerne (kernel). Sjekk at du har
aktivert gmgi221-miljøet og valgt riktig kjerne i Jupyter.
6. Oppsummering#
Konsept |
Nøkkelord |
|---|---|
Boolske uttrykk |
|
Betingelser |
|
Løkker |
|
Funksjoner |
|
Import |
|
Du trenger ikke huske alt utenat. Målet er at du skal kjenne igjen disse mønstrene når du ser dem i forelesningsnotebookene.
Avsluttende oppgave
Sett sammen det vi har vært gjennom. Skriv en funksjon
beskriv_punkt(lengdegrad, breddegrad) som:
bruker
utm_sone_epsgtil å finne riktig EPSG-kodesjekker om punktet ligger nord for polarsirkelen
returnerer en tekst som f.eks.
"Punktet (10.74, 59.91) hører til EPSG:25832 og ligger sør for polarsirkelen."
Test den med Oslo (10.738, 59.913) og Tromsø (18.955, 69.649).
# Din kode her:
Løsningsforslag
def beskriv_punkt(lengdegrad, breddegrad):
"""Kort tekstbeskrivelse: UTM-sone og forhold til polarsirkelen."""
epsg = utm_sone_epsg(lengdegrad)
if breddegrad > 66.5:
polart = "nord for polarsirkelen"
else:
polart = "sør for polarsirkelen"
return (
f"Punktet ({lengdegrad}, {breddegrad}) hører til "
f"EPSG:{epsg} og ligger {polart}."
)
print(beskriv_punkt(10.738, 59.913))
print(beskriv_punkt(18.955, 69.649))