💻 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/False

  • Betingelserif, elif, else: kjøre kode bare når noe stemmer

  • Løkkerfor: gjøre det samme for hvert element i en samling

  • Funksjonerdef: pakke kode i noe du kan gi navn, gjenbruke og teste

  • import – 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 sanne

  • or – sann hvis minst én side er sann

  • not – snur True til False og 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}.")

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:

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)

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:

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

  • Navngivingutm_sone_epsg(lengdegrad) sier hva koden gjør; ti linjer med if/elif gjør det ikke

  • Testing – en funksjon med tydelig inn- og utdata er lett å sjekke. Neste uke legger vi til assert i 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:

  • def starter definisjonen. Å definere funksjonen kjører den ikke – det skjer først når du kaller den, som polarsirkelen(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).

  • return sender en verdi tilbake til den som kalte funksjonen. Uten return gir funksjonen None.

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

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:

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

==, <, and, or, in

Betingelser

if / elif / else

Løkker

for ... in ...

Funksjoner

def, return

Import

import, from ... import, as

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:

  1. bruker utm_sone_epsg til å finne riktig EPSG-kode

  2. sjekker om punktet ligger nord for polarsirkelen

  3. 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: