Introduksjon til programmeringskurset
Vil du lære mer om programmering? 🎉
Dette er et programmeringskurs for deg som kan litt programmering fra før. Kurset starter med repetisjon av det grunnleggende, så ikke vær bekymret for at du kan for lite.
Vi forsøker å lære bort noen av de litt mer avanserte teknikkene og kunnskapene som du kan bruke til å forenkle oppgaver i og utenfor jobb. Kurset er praktisk orientert, gjennomføres i Python, og du følger kurset i det tempoet som passer deg. Er du allerede komfortabel med et tema så kan du bare hoppe forbi det.
Slik kommer du i gang
I dette kurset skal du bruke programmeringsspråket Python til å lage små applikasjoner. Derfor trenger du et par verktøy for å komme i gang:
- Installere Python (≥ 3.8) ved å følge instruksjonene på python.org, bruke pakkebehandleren (Linux/Unix) eller ved å bruke programvarekiosken (FileWave).
- Installere et verktøy som kan brukes til å skrive Python-kode. Om du ikke har noen preferanser anbefaler vi VS Code, med Python-plugin installert. Hvis du vil vite mer om redigering av pythonkode i VS Code, kan du ta en titt på denne guiden.
Sånn bruker du denne nettsida
Navigering
Kursmateriellet er delt opp i kapitler, som vi har organisert i ulike deler. Du kan hoppe rett til et kapittel ved å trykke på det i venstremenyen.
Venstremenyen er vanligvis skjult på mobil og når du har et smalt nettleservindu. Du kan åpne eller lukke menyen ved å trykke på meny-ikonet i det øvre, venstre hjørnet.
Du kan enkelt bla til forrige eller neste kapittel ved å bruke venstre- eller høyrepila. Du kan enten trykke på dem mot kanten av nettsida (eller i bunnen hvis du har et smalt nettleservindu), eller du kan bruke piltastene på tastaturet til å bla.
Endre fargetema
Ved å trykke på pensel-ikonet i det øvre, venstre hjørne kan du velge et fargetema. Du kan for eksempel bytte til et mørkt fargetema hvis du foretrekker det.
Søk
Du kan søke ved å trykke på søke-ikonet i det øvre, venstre hjørnet,
eller ved å trykke tasten S
på tastaturet.
Her kan du søke i alle delene av kursmateriellet.
Du kan velge et søkeresultat med musepekeren, eller med piltastene og [ENTER]
.
Når du har valgt et søkeresultat vil søkeordet være markert på sida.
Du kan bli kvitt markeringene ved å trykke på en av dem eller ved å trykke [ESC]
-tasten.
Lenker til kilden
Kilden til dette nettstedet er publisert på GitHub, som er et nettsted for deling av kode. Trykker du på GitHub-ikonet i det øvre, høyre hjørnet kan du utforske og lese hvordan kilden til nettsida er skrevet.
Ved siden av GitHub-ikonet er det også et redigeringsikon . Hvis du kommer over en skrivefeil eller en formulering som kan gjøres bedre, så kan du faktisk foreslå endringer direkte i GitHub ved å trykke på dette ikonet.
Innhold
- Introduksjon til Python
- Kommandolinjeapplikasjon
- Hente data fra API
- Applikasjon med grafisk brukergrensesnitt (GUI)
- Prosjektoppgave
- Ekstra - valgfritt innhold
Kurset er bygd opp slik at en del bygger videre på det man har lært i foregående delene.
Del 1 er en oppfriskning av grunnleggende Python, så om du allerede har dette i fingrene, kan du gå rett videre til del 2.
Bakgrunn
Denne nettsida utgjør kursmateriellet til et programmeringskurs som er laget av NRK-ansatte, med andre NRK-ansatte som målgruppe. Derfor bruker vi eksempler som vil være gjenkjennelige for folk i NRK, som for eksempel TV- og radioguiden. Når nettsida likevel er publisert åpent på internett, skyldes det at vi tror den kan være av nytte også for andre utenfor NRK.
Merk at kurset i blant bruker NRK sine interne API-er som eksempler på datakilder.
Du må selv sette deg inn i hvilke vilkår som gjelder for ulike datakilder.
For eksempel så har psapi.nrk.no
vilkår som ligger på https://psapi.nrk.no/.
Menneskene bak
Dette kursmateriellet er skrevet av:
- Heidi Mork
- Matias Pettersen
- Per Edvard Volla
- Teodor Ande Elstad
- Thorben Werner Sjøstrøm Dahl
Introduksjon til Python
💡 Læringsmål: I del 1 av kurset vil du lære hvordan du bruker Python, og de helt enkle verktøyene som finnes i dette programmeringsspråket.
Her er det bare å starte fra kapittel 1.1 og jobbe seg gjennom kapitlene, bit for bit, i ditt eget tempo. Kapitlene tar for seg det grunnleggende i Python, og tingene som gjennomgås her er byggeklossene for det vi skal lage senere i kurset.
Om dette er stoff du kjenner til fra før kan du vurdere å gå videre til del 2. I så fall kan det være lurt å se raskt igjennom kapitlene for å forsikre deg om at du ikke går glipp av noe.
- Kjøre Python-kode
- Hva er Python
- Kjøre Python interaktivt
- Navigere i filhierarkiet
- Skrive ditt første Python-skript
- Kjøre Python-skript
- Kommentarer
- Hvordan legge ved forklaringer i koden
- Enkle datastrukturer og variabler
- Tall (
int
,float
, f. eks.3.14
) - Variabler (
teller = 4
) - Strenger (
str
, f. eks."Hei!"
) - Boolske verdier (
bool
;True
ogFalse
) - Operasjoner på enkle datastrukturer
- Tall (
- Samlinger
- Lister (list, f.eks
[1, 2, 3]
) - Tupler (tuple, f.eks
(1, "a")
) - Oppslagstabeller (dict, f.eks
{ "navn": "Vibeke", "alder": 54 }
)
- Lister (list, f.eks
- Ta inn data fra bruker
- Bruk av
input
- Tolke data som tall
- Stille ja/nei-spørsmål
- Bruk av
- Hvis, omatte og ellers
if
,elif
ogelse
- Innrykk og kodeblokker
- Spørre bruker før programmet fortsetter
- Tilegne ulik verdi basert på boolsk uttrykk
- Løkker
- For-løkker (
for x in y:
) - While-løkker (
while kriterie:
) - Generators (
range()
)
- For-løkker (
- Funksjoner
def
- Parameter og argument
- Returverdier
Kjøre Python-kode
💡 Læringsmål: I dette kapittelet skal du lære deg hvordan du får datamaskinen din til å kjøre Python-kode.
Når man lærer seg et programmeringsspåk, er ofte det første programmet man skrivet et «Hallo, verden»-program. Dette er helt enkelt et program som skriver ut teksten «Hallo, verden». La oss skrive et sånt program sammen!
Hva er Python?
Python er et programmeringsspråk som fikk sin spede begynnelse i juleferien til nederlenderen Guido van Rossum i 1989. Han publiserte prosjektet på internett (Usenet) i 1991, og det har siden blitt etablert en ideell stiftelse som videreutvikler og forvalter Python. Guido van Rossum tok navnet fra et manuskript han leste i tida rundt da prosjektet startet, nemlig Monty Python's Flying Circus.
Språket er i dag mye brukt i introduksjonskurs i programmering. Det er likevel ikke begrenset til lek og undervisning; Python er mye brukt innenfor områder som vitenskap, maskinlæring (KI), og nettsideutvikling. I NRK brukes Python blant annet for diverse støttesystemer rundt planleggings- og rapporteringssystemet PRF, og til å styre transkodingsprosessene for podkast- og radio-lydfiler.
Les mer om Python i Store norske leksikon.
Python som programmeringsspråk
Når vi programmerer Python, lager vi vanligvis tekstfiler med instruksjoner skrevet i Python-programmeringsspråket.
Filene har filendelsen .py
.
Språket følger en bestemt syntaks, som sier noe om hvordan gyldig Python-kode skal se ut, og en semantikk, som sier noe om hva datamaskinen skal gjøre når den kjører koden.
Python-kode kan skrives i et hvilket som helst program som produserer rene tekstfiler, som for eksempel Notepad/Notisblokk på Windows. Vi anbefaler dog at du bruker et verktøy som er spesialdesignet for å skrive Python-kode, siden du får hjelp til å se hvordan koden blir tolket av datamaskinen. Introduksjonen til kurset har anbefalinger for deg som ikke har en kodeeditor fra før.
Python som dataprogram
Python er også navnet på et dataprogram, som formelt sett heter CPython. CPython er en fortolker, på engelsk interpreter. Fortolkere tolker tekstfiler med kode, og har ansvar for å bringe koden du skriver til live. Når vi vil kjøre Python-kode, må vi altså starte en Python-fortolker, og be den om å tolke tekstfila vi har skrevet og utføre instruksjonene der. Det finnes også andre fortolkere som kan tolke Python-koden vi skriver, men vi holder oss til CPython i dette kurset.
I introduksjonen står det instruksjoner for hvordan du kan installere Python-fortolkeren. Når det er installert, skal vi kunne starte det fra terminalen.
Kjøre Python-fortolkeren i terminalen
Start en terminal som Powershell på Windows, Terminal på Mac eller noe tilsvarende på Linux. Du vil få et nesten tomt vindu med en blinkende markør. Terminalen venter på at du skal skrive en kommando som den skal kjøre.
I hele dette kurset skal vi starte Python-fortolkeren fra terminalen. Men nøyaktig hva fortolkeren heter, kommer an på hvilket operativsystem du bruker, og hvordan du har installert Python.
OS | Installasjonsmåte | Navnet på Python-fortolkeren |
---|---|---|
Windows | Python.org | py |
Windows | Filewave | python |
Windows | Windows Store | python |
Linux | Pakkebehandler | python3 |
MacOS | Python.org | python3 |
Merk: I alle eksemplene kommer vi til å kalle fortolkeren for python
, men det bytter du bare ut med riktig navn når du kjører kommandoer hos deg.
La oss teste at Python er installert ved å skrive python --version
i terminalen og trykke [ENTER]
:
$> python --version
Python 3.10.2
Funker det ikke? Se neste seksjon for vanlige problemer og løsninger.
Du vil sannsynligvis få en annen versjon hos deg, men så lenge den er nyere enn eller lik 3.8 skal alle delene av kurset fungere.
Mulige feil og løsninger når du kjører Python i terminalen
❌ Når jeg kjører python --version
får jeg til svar Python 2.7
eller en annen gammel variant
Prøv å kjøre python3
i stedet for python
:
$> python3 --version
Python 3.10.12
❌ Jeg får feilmelding Python ble ikke funnet; kj°r uten argumenter for Õ installere fra Microsoft Store, eller deaktiver denne snarveien fra Innstillinger > Administrer app utf°relses aliaser
.
Har du installert fra Python.org? Prøv å kjøre med py
i stedet for python
:
$> py --version
Python 3.11.5
❌ Jeg får feilmelding NameError: name 'python' is not defined
Starter skrivelinja i terminalen med >>>
?
Da er du allerede inne i en interaktiv Python-sesjon.
Hvis du vil tre ut av den skriver du exit()
og trykker [ENTER]
– da kommer du tilbake til utgangspunktet og kan kjøre kommandoer igjen.
❌ Jeg får feilmelding python: command not found
eller python: The term 'python' is not recognized as a cmdlet, …
- Prøv med andre varianter, som
py
ellerpython3
- Er du sikker på at du har installert Python?
- Det kan hende Python ligger i ei mappe som terminalen ikke er satt opp til å søke i. Prøv å installere på nytt, men huke av for valg om å legge til Python i "PATH".
❌ Jeg får feilmelding fra py
om at No suitable Python runtime found
- På Windows blir Python-installasjonen vanligvis installert personlig. Åpnet du terminalen med den samme brukeren som du installerte Python med?
- Du kan eventuelt installere Python på nytt, men velge «Customize install» – da kan du velge å installere Python for alle brukerne på maskinen, og ikke bare din egen.
- Du kan også få denne feilmeldingen hvis du har installert fra Python.org, men seinere fjernet Python-installasjonen.
Da kan
py
bli liggende igjen, men uten at den har noen Python-fortolker å bruke. Prøv å installere på nytt.
Interaktiv Python: Test ut ting rett i terminalen
Fikk du kjørt python --version
(eller python3 --version
eller py --version
) ovenfor?
Da er vi i så fall klare til å starte Python-fortolkeren interaktivt.
I interaktiv modus skriver du én Python-linje om gangen. Python-fortolkeren kjører koden, og skriver resultatet tilbake til deg i terminalen.
For å starte Python-fortolkeren i interaktiv modus, skriver du bare python
(eller python3
eller py
) uten noe etter:
$> python
Python 3.10.12 (main, Jun 7 2023, 12:45:35) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
Du kan se at Python-fortolkeren venter på at du skal skrive din første Python-linje.
Du kan se det på grunn av de tre «større enn»-tegnene, >>>
.
La oss prøve ut litt Python-kode!
Skriv inn print("Hallo, verden")
og trykk [ENTER]
.
Hva skjedde?
Resultatet av print("Hallo, verden")
$> python
Python 3.10.12 (main, Jun 7 2023, 12:45:35) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print("Hallo, verden")
Hallo, verden
>>>
Du kan også regne ut mattestykker!
Prøv for eksempel å skrive 6 * 7
og trykke [ENTER]
.
Resultatet av 6 * 7
>>> 6 * 7
42
>>>
Du kan se over ekstra-delen om interaktiv Python litt seinere i kurset hvis du er nysgjerrig på andre ting du kan gjøre med interaktiv Python.
Forlate interaktiv Python
For å avslutte Python-fortolkeren skriver du exit()
etterfulgt av [ENTER]
:
>>> exit()
$>
Nå har python
-kommandoen vi starta kjørt ferdig, og vi er tilbake til utgangspunktet.
Herfra kan vi kjøre andre kommandoer, eller vi kan kjøre python
igjen.
Filer og mapper
Interaktiv Python er bra for eksperimentering og lek, men til daglig er det best å skrive koden inn i en fil. Da er det mye lettere å gjøre små justeringer og kjøre koden på nytt igjen, spesielt når du får logikk som går over mer enn én linje.
Filer bor i mapper, og det kan være nyttig å lage en mappe som inneholder alle filene du skal lage i dette kurset. Vi kommer til å bruke en mappe som heter kurs/
i denne kursbeskrivelsen. I denne mappen kan du lage en fil som heter hallo_verden.py
. Når du har gjort det, kan du åpne filen i VS Code, og legge inn koden som er vist under:
print("Hallo, verden")
Når du har lagt inn koden i filen, lagrer du den og starter en terminal som Powershell på Windows, Terminal på Mac eller noe tilsvarende på Linux.
I den åpne terminalen, navigerer du ned i kurs/
-mappen med f.eks. kommandoen cd
.
$> cd kurs/
kurs $>
Det kan være du må navigere lenger enn bare rett ned i kurs/
-mappen på din maskin. Dette er avhengig av hvilken mappe terminalen åpnet seg i. For å gå ut av en mappe kan man bruke kommandoen cd ..
. Avhengig av hvilket operativsystem du bruker, kan det også være mulig å åpne terminalen direkte i kurs/
-mappen, fra filutforskeren.
Da skal vi kjøre Python-koden! Dette gjør du helt enkelt ved å kjøre kommandoen python
etterfulgt av et mellomrom og navnet på Python-fila:
kurs $> python hallo_verden.py
Hallo, verden
Gratulerer! Du har nå kjørt ditt første Python-skript.
❌ Får du feil? Sjekk listen over vanlige feil ovenfor. Du må for eksempel gå ut av interaktiv Python før du får kjørt Python-skript.
Hva er et Python-skript?
En .py
-fil, ofte kalt et Python-skript, er en fil som inneholder Python-kode.
Disse skriptene får ikke til å gjøre noe med datamaskinen din på egenhånd; de er fullstendig avhengige av at Python-fortolkeren leser dem og utfører instruksjonene i dem.
Begrepet skript stammer fra manuskript, og du kan sammenlikne Python-skript med manuskript som skuespillere fremfører på scenen. Manuskriptet alene er ikke nok til å utgjøre en forestilling; det er først når du kombinerer manuskriptet med skuespillere, rekvisitører, teknikere og alle rundt at du får en forestilling.
(Foto av Nationaltheatret av Chris Nyborg. Foto av «Et dukkehjem»-forestilling av Otterbein University Theatre & Dance. Python-logo av Tango-prosjektet.
Alle tre er lisensiert under Creative Commons BY-SA. Denne montasjen er satt sammen av Thorben Werner Sjøstrøm Dahl og er lisensiert under Creative Commons BY-SA 3.0.)
Selve Python-språket er det samme, uansett om du kjører Python-fortolkeren interaktivt i terminalen eller du skriver koden i et skript som du ber fortolkeren om å tolke. Du kan i teorien sitte og ta én og én linje fra et Python-skript og fôre dem til en interaktiv Python-sesjon, og resultatet vil bli nøyaktig det samme.
NB: I dette kapittelet har vi brukt begrepet Python-fortolker om dataprogrammet som tolker og kjører Python-koden den blir gitt, og Python-skript om .py
-filene som inneholder Python-kode.
I resten av kurset kan vi finne på å si at «Vi lager et program som skal lese en tekstfil», og bruker «program» og «(Python-)skript» om hverandre.
Når vi bare snakker om «Python» kan vi referere til språket eller til Python-fortolkeren.
Oppsummert:
Fagterm | Engelsk | Forklaring | Kalles også |
---|---|---|---|
Python-fortolkeren | the Python interpreter | Dataprogram som tolker og kjører Python-kode | Python, CPython |
et Python-skript | a Python script | .py -filer som inneholder Python-kode | et skript, et program, en applikasjon |
Sånn printer du til terminalen
I programmet over brukte vi funksjonen print(...)
for å få Python-fortolkeren til å skrive tekst til terminalen.
Dette er ofte den enkleste måten å vise frem data på.
Man kan få Python-fortolkeren til å skrive tekst til terminalen med print(...)
stort sett hvor som helst i et Python-skript, så denne funksjonen kan være nyttig i mange sammenhenger.
print(...)
er et eksempel på en innebygget funksjon i Python. Funksjoner skal vi lære mer om senere i kurset, blant annet hvordan du kan lage dine egne funksjoner, men nå i første omgang kan vi notere oss tre ting:
- Funksjonen har ett navn,
print
, og dette navnet står først, sånn at Python-fortolkeren forstår hvilken funksjon vi ønsker å bruke. - Parentesene etter funksjonsnavnet, står rundt teksten vi sender inn til funksjonen, sånn at Python-fortolkeren vet hvilken tekst vi ønsker å skrive til terminalen.
- Selve teksten er skrevet med anførselstegn (
"
). Dette forteller fortolkeren at det her er snakk om tekst, og ikke mer programkode.
✍️ Oppgave: Kan du utvide hallo_verden.py
-skriptet, sånn at det printer ut en tekst til?
Kommentarer
💡 Læringsmål: I dette kapittelet skal du lære deg å skrive forklaringer som kan bo sammen med koden.
Korte kommentarer
Kommentarer er kode som ikke gjør noe annet enn å være forklarende tekst til den som leser koden. Teksten i kommentarene kjøres ikke som en del av dataprogrammet, men er kodelinjer som Python-fortolkeren "hopper over".
For å skrive en kommentar på en linje i et Python-skript, starter man linjen med et nummertegn (#
). Da vil Python-fortolkeren forstå at resten av teksten på linjen er en kommentar, og ikke programkode.
# Dette er en kommentar
✍️ Oppgave: Kan du skrive en kommentar på hva du prøver å få til med en print
-linje i hallo_verden.py
Lange kommentarer
Når man trenger å skrive lange kommentarer, kan det være nyttig å dele de opp over flere linjer. En måte å få til dette på, er å legge inn nummertegn i starten av alle linjene.
# Når du har mye på hjertet,
# kan det være nyttig
# å dele opp kommentaren over flere linjer.
Et annet alternativ er å bruke tre anførselstegn ("""
) på en linje før og etter kommentaren. Da trenger man ikke å legge til noe i starten av hver linje.
"""
Når du har mye på hjertet,
kan det være nyttig
å dele opp kommentaren over flere linjer.
"""
Kommentere ut kode
I tillegg til å være forklaringer, kan man bruke kommentarer til å fjerne deler av koden mens man utvikler et program. Dette kan være nyttig når man kjapt vil sjekke hva som skjer hvis bare deler av koden kjører. Når man bruker kommentarer på denne måten, sier man gjerne at man "kommenterer ut kode".
# print("Denne koden er kommentert ut")
✍️ Oppgave: Kan du kommentere ut en print
-linje i hallo_verden.py
, og observere hva som da skjer når du kjører programmet?
Enkle datastrukturer og variabler
💡 Læringsmål: I dette kapittelet skal du lære deg å bruke enkle datastrukturer som tall, tekst og boolske verdier. I tillegg skal vi se litt på variabler.
Når vi snakker om datastrukturer, snakker vi i grove trekk om mekanismene vi bruker for å organiserer data i et dataprogram. Ofte skiller vi mellom enkle datastrukturer, som typisk er bygget inn i programmeringsspråket, og mer avanserte datastrukturer, hvor programmereren selv bygger datastrukturen basert på egendefinerte klasser, objekter og funksjoner.
I dette kapittelet skal vi se på enkle datastrukturer som tall, strenger (tekst) og boolske verdier (sant og falskt). I tillegg skal vi se på variabler, som lar oss referere til dataverdier med navn.
Tips: Når du går gjennom dette kapittelet, kan det være lurt å lage en kodefil som heter datastrukturer.py
i mappen kurs/
, og bruke denne til å teste ut de forskjellige kodesnuttene du støter på.
Tall
Python har innebygget støtte for å jobbe med tall. De to viktigste typene tall er heltall (integers på engelsk, eller int
i kode) og flyttall (floating-point numbers på engelsk, eller float
i kode).
Både heltall og flyttall skriver man rett i koden som tall. Som desimaltegn bruker man punktum (.
).
# Skriver ut heltallet 3 (tre)
print(3)
# Skriver ut flyttallet 4,5 (fire og en halv)
print(4.5)
For å skrive negative tall, skriver man et minustegn (-
) før tallet.
# Skriver ut det negative heltallet -5 (minus fem)
print(-5)
# Skriver ut det negative flyttallet -7,2 (minus syv komma 2)
print(-7.2)
Vi regner med tall
Python har flere innebygde funksjoner for å regne med tall:
+
lar oss legge sammen to tall.1 + 1
blir eksempelvis2
.-
lar oss trekke et tall fra et annet.3.4 - 2
blir eksempelvis1.4
.*
lar oss multiplisere to tall.4 * 3
blir eksempelvis12
./
lar oss dele ett tall på et annet.2 / 1.5
blir eksempelvis1.3333333333333333
.
I motsetning til funksjonene vi har blitt kjent med fra før, skriver man de vanligste matematiske funksjonene mellom de to tallene man regner på. Dette er forskjellig fra hvordan man bruker funksjoner ellers i Python, men er noe man har gjort for at matematiske uttrykk skal ligne mer på det man er vant til fra annen matematikk.
Funksjonene over fungerer både med heltall og flyttall, og returnerer heltall eller flyttall, avhengig av om svaret på utregningen er et desimaltall, eller om man brukte et flyttall for å regne ut svaret.
✍️ Oppgave: Kan du skrive litt kode i datastrukturer.py
som bruker de fire innebygde matematiske funksjonene over?
Vanlige og spesielle matematiske funksjoner
Mange matematiske funksjoner finnes både som "vanlige" Python-funksjoner og som "spesielle" funksjoner som man skriver mellom to tall. Et eksempel på dette er pow(...)
-funksjonen, som regner ut potensen til et tall. Dette kan også gjøres med den "spesielle" funksjonen **
.
# Regner ut 2 opphøyd i 3. potens med pow-funksjonen
print(pow(2, 3))
# Regner ut 2 opphøyd i 3. potens med **
print(2 ** 3)
Noen matematiske funksjoner finnes bare som "vanlige" Python-funksjoner. Et eksempel på dette er math.sqrt(...)
, som regner ut kvadratroten til et tall.
# Importerer biblioteket math, som inneholder flere matematiske funksjoner
# Import av biblioteker er noe vi ikke skal gå inn på nå, men som forklares i senere kapitler
import math
# Regner ut kvadratroten av 9
print(math.sqrt(9))
Regnerekkefølge
De matematiske funksjonene i Python, evalueres i utgangspunktet i en bestemt rekkefølge. Dette er grunnen til at 3 + 2 * 6
blir 15
(2 * 6
blir 12
, og legger man til 3
får man 15
).
For å styre rekkefølgen uttrykk evalueres i, kan man bruke parenteser ((
og )
). Da kan man eksempelvis skrive (3 + 2) * 6
, sånn at tallene 3
og 2
legges sammen først, før man multipliserer med 6
.
Noen ganger kan det være lurt å legge til parenteser, selv om uttrykket evaluerer i den rekkefølgen man ønsker i utgangspunktet. Eksempelvis blir både 3 + 2 * 6
og 3 + (2 * 6)
evaluert til 15
, men i det andre uttrykket kan det være enklere å se at multiplikasjonen evalueres først.
Oversette mellom heltall og flyttall
Ofte er det ikke viktig om man bruker heltall eller flyttall når man regner, men noen ganger gjør det hele forskjellen. Et eksempel på dette kan være at man skal slå opp noe på en spesifikk plass i en liste, noe vi kommer til senere i kurset.
Hvis man skal oversette et heltall til et flyttall, kan man bruke funksjonen float(...)
.
# Oversetter heltallet 3 til flyttallet 3.0
print(float(3))
Når man skal oversette et flyttall til et heltall, er det flere funksjoner som er vanlige å bruke:
int(...)
kutter av alle desimalene, så bådeint(1.2)
ogint(1.8)
blir til1
.round(...)
runder av tallet, såround(1.2)
blir til1
oground(1.8)
blir til2
. Man kan også sende inn et ekstra tall tilround(...)
, hvis man ønsker å ta vare på et spesielt antall desimalerround(1.333333333333333333, 2)
blir til1.33
.//
er nyttig hvis man skal dele to tall, og bare ønske å få tilbake tallene før komma. Eksempelvis blir2 // 3
til0
, og5 // 3
blir til1
. Man kan med andre ord tenke på//
som en funksjon som først deler tallene med/
, og deretter kutter av desimalene medint(...)
.
Enda flere matematiske funksjoner
Modulus er noe vi kanskje ikke bruker så mye i det daglige, men som har en tendens til å dukke opp i programmering titt og ofte. Kort fortalt er dette hvor mye som er igjen av et tall, hvis vi forsøker å dele det med et annet. I Python kan vi finne denne med å bruke funksjonen %
. Eksempelvis blir 5 % 2
til 1
, fordi man kan trekke 2
fra 5
to hele ganger, og så ender man opp med en rest på 1
. 10 % 6
blir til 4
, fordi man kan trekke 6
fra 10
en gang, og da ender man opp med en rest på 4
.
Modulus er nyttig for å f.eks. finne ut om et tall er et partall. Vi vet at partall kan deles på 2, så derfor må et partall sin % 2
være lik 0
.
En annen funksjon som stadig vekk er nyttig når man programmerer, er abs(...)
, som finner absoluttverdien til et tall. Absoluttverdien er kort fortalt hvor stort tallet er, hvis vi ser bort ifra om det er positivt eller negativt. Eksempelvis er både abs(3)
og abs(-3)
tallet 3
.
🧠 Visste du at? Flyttall er ikke helt presise
Flyttall blir lagret på en måte som gjør at de ikke blir helt presise. Dette er en generell egenskap ved flyttall.
Prøv for eksempel å regne ut 0.1 + 0.1 + 0.1
:
$> python
Python 3.10.2 (tags/v3.10.2:a58ebcc, Jan 17 2022, 14:12:15) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> 0.1 + 0.1 + 0.1
0.30000000000000004
>>> exit()
$>
Det finnes gode forklaringer på hvorfor, men de er litt kompliserte og handler om hvordan man lagrer ting i totallssystemet (hvor du bare har 0
og 1
).
Så lenge du bare bruker heltall (int
) trenger du ikke bekymre deg over denne mangelen på presisjon.
Trenger du presisjon på desimaltall så finnes det et alternativ for avansert bruk, men de aller fleste trenger ikke forholde seg til dette.
Variabler
Når man programmerer, skiller man gjerne mellom variabler og verdier. Verdier er ofte det som representerer data i et program, og er gjerne noe spesifikt, som tallet 3
, eller teksten "hei alle sammen"
. Når vi lærte om tall i seksjonen over, var alle tallene vi brukte eksempel på verdier.
Variabler lar oss gi verdier et navn. Ved å gi verdier navn, kan vi bedre kommunisere hva en verdi representerer. Med variabler kan vi skrive kode som regner med antall_epler
og antall_appelsiner
, i stedet for tall som 3
og 5
. Da slipper vi å huske hva hvert tall representerer.
Variabler gir oss også muligheten til å skrive kode som fungerer for mange forskjellige verdier. Man kan f.eks. lage to variabler, et_tall
og et_annet_tall
, som inneholder to tallverdier som en bruker har skrevet inn. Så kan man bruke disse variablene til å printe ut summen av tallene med print(et_tall + et_annet_tall)
. Denne koden vil da fungere uansett hvilke tall brukeren har skrevet inn.
Livet til en variabel
Livet til en variabel består av fire faser:
- Variabelen oppstår når vi gir den et navn.
- Når variabelen har et navn, kan vi gi den en verdi.
- Når variabelen har en verdi, kan vi bruke variabelnavnet til å hente verdien i koden vår.
- Når det ikke lenger er behov for variabelen, kan programmet fjerne den.
Variabelen oppstår og får sin første verdi
I Python gjennomgår variabler alltid livets to første faser samtidig. Det betyr at vi alltid må gi variabler en verdi samtidig som vi gir de navn. Dette gjør vi ved å skrive navnet på variabelen vi ønsker å lage, et likhetstegn =
, etterfulgt av verdien vi vil at variabelen skal ha. Kodelinjen en_variabel = 3
forteller dermed Python at vi ønsker å opprette en variabel med navnet en_variabel
, og at vi ønsker at den skal få verdien 3
.
en_variabel = 3
print(en_variabel)
Hvorfor kan jeg ikke lage en variabel uten en verdi i Python? Variabler er stort sett bare nyttig når de inneholder en verdi. Derfor krever Python at man gir en variabel en verdi når man lager den. Dette gjør at en del feil er litt mindre vanlige i Python, enn i andre programmeringsspråk hvor man kan lage variabler som ikke inneholder verdier.
Som variabelnavn kan man bruke kombinasjoner av små og store bokstaver, tall og understrek (_
). Det eneste begrensningen er at eventuelle tall ikke kan være det første tegnet i variabelnavnet. Hvis man har variabelnavn som består av flere ord, bruker man gjerne understrek for å skille hvert ord, f.eks. et_stort_tall
. Tall brukes gjerne der man har flere variabler med lignende navn, som f.eks. heltall_1
og heltall_2
.
✍️ Oppgave: Hvilke variabelnavn er gyldige? Under er det en liste med variabelnavn. Klarer du å finne ut av hvilke variabelnavn som er gyldige i Python? Hvis du er i tvil, kan du skrive litt kode i datastrukturer.py
som forsøker å lage en variabel med det variabelnavnet du lurer på er gyldig.
Variabelnavn:
en_annen_variabel
en-variabel
3_variabler
variabel_3
enVariabel
EnVariabel
en variabel
nå_er_det_nok
Vi bruker variabelen
Når man har laget en variabel, og gitt den en verdi, kan man bruke variabelen akkurat som om den var verdien. Det betyr at hvis man har to variabler som inneholder tall-verdier, så kan man bruke de variablene akkurat som om de var tall.
print(3 + 7)
et_tall = 3
et_annet_tall = 7
print(et_tall + et_annet_tall)
Man kan også gi variabler en verdi som er hentet fra andre variabler, eller er resultatet av en funksjon som bruker andre variabler eller verdier.
et_tall = 3
et_annet_tall = 7
summen_av_to_tall = et_tall + et_annet_tall
print(summen_av_to_tall)
✍️ Oppgave: Kan du skrive litt kode i datastrukturer.py
som bruker de matematiske funksjonene vi har lært om, men bruker variabler til å ta vare på de tallene man regner med, og resultatene man får?
Kan variabler endre verdi?
I Python kan man gi en variabel en verdi flere ganger. Det betyr at en variabel kan starte med å inneholde tallet 3
, og senere i koden kan inneholde et annet tall som 7
.
en_variabel = 3
print(en_variabel)
en_variabel = 7
print(en_variabel)
En variabel kan også inneholde helt forskjellige typer verdier. Man kan f.eks. starte med å la variabelen inneholde et tall 3
, og så la den inneholde teksten "hei igjen"
.
en_variabel = 3
print(en_variabel)
en_variabel = "hei igjen"
print(en_variabel)
Selv om det kan være nyttig å endre verdien til en variabel, kan det også være veldig forvirrende. Spesielt forvirrende kan det være om man lar variabelen endre seg fra helt to forskjellige typer verdier. Derfor kan det være lurt å ha disse to tommelfingerreglene i bakhodet:
- Hvis det ikke er en spesiell grunn til å endre verdien på en variabel, bruk heller to eller flere variabler.
- Hvis man må endre på verdien til en variabel, forsøk å skrive koden sånn at man ikke trenger å endre på hvilken type verdi variabelen inneholder.
Tips: Alle regler er til for å brytes, og reglene over er intet unntak. Det finnes mange tilfeller hvor det kan være nyttig å endre verdien til en variabel, og du kommer til å støte på flere eksempler på kode som gjør det i dette kurset.
Når det ikke lenger er behov for en variabel
Python er et eksempel på et programmeringsspråk hvor man i liten grad trenger å skrive kode som forteller programmet at det ikke lenger er behov for en variabel. Dette klarer Python i stor grad å finne ut selv.
Grunnen til at det er viktig for programmeringsspråk å vite at variabler ikke lenger er i bruk, er fordi det lar programmet rydde opp mens det kjører. Da kan programmet f.eks. slutte å bruke minne til å huske på et stort tall, eller "låse opp" en fil, sånn at andre deler av programmet kan skrive til den.
Når man starter å programmere, er det ikke så viktig å tenke på at ting ryddes opp, men etter hvert som man skriver mer avanserte programmer, som f.eks. skal jobbe med filer som er større enn RAM-minnet til datamaskinen, må man i større grad ta hensyn til sånne ting.
I senere kapitler, kommer du også til å bli kjent med Python-kode hvor man forteller Python hvor lenge det er behov for en variabel. Da bruker man typisk syntaksen with verdi as variabelnavn:
, som forteller Python at denne variabelen bare skal leve innenfor skopet til den blokken with
-uttrykket omfavner. Ikke fortvil om dette ikke gir så mye mening nå, vi kommer til å jobbe mer med det senere.
Strenger
Skal man jobbe med tekst, er strenger en fin datastruktur. Kort forklart er en streng en sekvens av tegn (bokstaver, tall eller andre skrifttegn som punktum og komma).
Den enkleste måten å lage en streng på, er ofte å bruke anførselstegn ("
) eller apostrof ('
):
en_streng = "Hallo verden"
print(en_streng)
en_annen_streng = 'Hei igjen'
print(en_annen_streng)
Anførselstegn og apostrof
Når man lager en streng på denne måten, er det viktig at teksten man omslutter med anførselstegn eller apostrof, ikke selv inneholder anførselstegn eller apostrof. Hvis man gjør det, blir Python forvirret og tror at teksten slutter før den egentlig gjør det.
# Dette fungerer ikke
funker_ikke = 'Snurp igjen smella di, du maser jo som et lok'motiv'
funker_heller_ikke = "Fra filmen "Lasse & Geir" av Wam og Vennerød"
For å få til dette, kan man passe på at man omslutter tekst som inneholder anførselstegn med apostrof, og vice versa.
# Dette fungerer helt fint
funker_fint = "Snurp igjen smella di, du maser jo som et lok'motiv"
funker_også_fint = 'Fra filmen "Lasse & Geir" av Wam og Vennerød'
Hvis det ikke holder å bytte mellom anførselstegn og apostrof, kan man også bruke bakstrek (\
) for å fortelle Python at et enkelt tegn skal tolkes som en del av strengen.
# Her bruker vi bakstrek (\) som rømningskarakter (escape character)
funker_fint = "Snurp igjen smella di, du maser jo som et lok\'motiv. Fra filmen \"Lasse & Geir\" av Wam og Vennerød"
Strenger med flere linjer tekst
Noen ganger har man lyst på en tekst som inneholder linjeskift. For å få til dette, må man legge inn et eget tegn for linjeskift (\n
) der man ønsker det i teksten.
flere_linjer = "Hva brast så høyt der?\nNorge av di hand, konge."
print(flere_linjer)
Et annet alternativ er å omslutte teksten med tre apostrofer ('''
). Da kan man skrive inn linjeskift som vanlig i teksten.
flere_linjer = '''Hva brast så høyt der?
Norge av di hand, konge.'''
print(flere_linjer)
Formaterte strenger
Strenger i seg selv er ganske nyttig, men de blir enda nyttigere når vi fyller de med data fra andre variabler. Den mest praktiske måten å gjøre dette på, er å bruke formaterte strenger.
Formaterte strenger, eller f-strenger som de ofte kalles, skiller seg fra vanlige strenger på to måter:
- De starter med bokstaven
f
før det første anførselstegnet eller apostrofen. - Inne i strengen kan man bruke krøllparenteser (
{
og}
) for å omslutte variabler eller kode som man vil flette inn verdien av.
et_tall = 3
et_annet_tall = 7
svar = f"Summen av {et_tall} og {et_annet_tall} er {et_tall + et_annet_tall}"
print(svar)
✍️ Oppgave: Kan lage en egen formatert streng i datastrukturer.py
som setter sammen noen variabler og printer de til terminalen?
Når man fletter inn en verdi eller variabel i en formatert streng, vil Python forsøke å gjøre om verdien man fletter inn til en streng. For enkle datastrukturer som tall, vil man typisk få en passende streng ut av boksen, men for mer avanserte datastrukturer, kan det være man må gjøre konverteringen selv, eller bruke en av konverteringsfunksjonene som er bygget inn i f-strenger.
Formaterte strenger er litt nyere funksjonalitet i Python. Før dette var tilgjengelig, brukte man gjerne funksjonen +
for å slå sammen strenger og verdier. Dette er typisk litt mer kronglete enn å bruke f-strenger, men kommer du over gammel kode, kan det være du ser denne måten å bygge strenger på.
Andre nyttige streng-funksjoner
Det finnes veldig mange nyttige funksjoner som kan brukes til å behandle strenger i Python. Vi rekker ikke å dekke alle her, men lurer du på hvordan man kan gjøre noe spesielt med en streng, er det ofte verdt å ta en titt i dokumentasjonen. Under har vi trukket frem tre funksjoner som er nyttig å vite om.
Noen ganger lurer man på om en streng inneholder en spesiell tekst. For å finne ut av dette, kan man bruke funksjonene in
og not in
.
en_streng = "Hallo verden"
print("Hallo" in en_streng) # Skriver ut True
print("Hei" in en_streng) # Skriver ut False
print("Hei" not in en_streng) # Skriver ut True
For funksjonene in
og not in
, gjør store og små bokstaver en forskjell. Ordet Hallo
er f.eks. ikke det samme som hallo
.
en_streng = "Hallo verden"
print("hallo" in en_streng) # Skriver ut False
Hvis man ønsker å se bort ifra store og små bokstaver når man sammenligner strenger, kan man bruke funksjonen casefold. Denne gjør om bokstavene i strengen til små bokstaver, så man kan sammenligne den med andre strenger uten å tenke på om den inneholder store og små bokstaver.
en_streng = "Hallo verden"
print("hallo" in en_streng.casefold()) # Skriver ut True
Verdiene True
og False
i koden over, er eksempler på boolske verdier. Dette skal vi lære om i neste seksjon.
Det kan også være nyttig å dele opp en streng i flere biter. For å gjøre dette, kan man bruke funksjonen split.
en_streng = "Hallo verden"
print(en_streng.split()) # Skriver ut ['Hallo', 'verden']
I grove trekk, deler split opp strengen basert på mellomrom. Dette kan ofte være et greit utgangspunkt hvis man forsøker å dele en tekst opp i flere ord.
Hvis man heller ønsker å dele opp strengen basert på et annet tegn, kan man gjøre dette ved å sende tegnet inn som et argument til split-funksjonen.
en_streng = "Hallo verden"
print(en_streng.split("r")) # Skriver ut ['Hallo ve', 'den']
Tips: Strenger er en veldig nyttig og fleksibel datastruktur, men siden den kan brukes til nesten hva som helst, er den også lett å misbruke. Ender du opp med å gjøre mange avanserte operasjoner på strenger, kan det være lurt å tenke på om man heller skulle brukt en annen datatype.
Boolske verdier
Boolske verdier, er verdier som enten kan være sanne (True
) eller falske (False
). De brukes gjerne der man ønsker at et dataprogram skal velge mellom å gjøre to forskjellige ting. Hvordan man får programmer til å velge mellom forskjellige ting, basert på en boolsk verdi, er noe vi skal se på i et senere kapittel. I første omgang skal vi bare se litt på hva boolske verdier er, og hvordan man kan bruke ulike boolske funksjoner til å lage boolske uttrykk, som igjen regner ut nye boolske verdier.
Sant og falskt
En boolsk verdi kan bare være en av to ting. Enten er den sann (True
), eller så er den falsk (False
).
sant = True
falskt = False
print(f"Noen ting er {sant} og noen ting er {falskt}")
Dette kan virke ganske begrenset, men som vi skal se senere i kurset, er det overraskende mange ting man kan få til med en verdi som bare kan være True
eller False
.
Ikke
Hvis man ønsker å "snu om" på en boolsk verdi, kan man bruke funksjonen not
. Denne funksjonen tar inn en boolsk verdi, og gir tilbake det motsatte.
For å forstå hvordan not
fungerer, kan vi sette opp en sannhetstabell. Denne tabellen har forskjellige kolonner som inneholder forskjellige boolske verdier, og boolske uttrykk som bruker de forskjellige verdiene. Hver rad i tabellen viser hva det boolske uttrykket blir, hvis de boolske verdiene er satt til en spesiell kombinasjon av True
eller False
.
a | not a |
---|---|
True | False |
False | True |
Tabellen over, viser at hvis variabelen a
er satt til False
, vil det boolske utrykket not a
bli verdien True
.
sant = True
print(f"Noen ting er {sant} og noen ting er {not sant}")
Og
Hvis man ønsker å finne ut av om to variabler er sanne samtidig, kan man bruke funksjonen and
. Denne funksjonen tar inn to boolske verdier, og gir tilbake True
hvis begge verdiene den tok inn er True
.
a | b | a and b |
---|---|---|
True | False | False |
False | True | False |
False | False | False |
True | True | True |
a = True
b = False
print(f"Kommer jeg til å bli sann eller falsk? {a and b}")
Eller
Hvis man bare er opptatt av å finne ut om en av to variabler er sanne, kan man bruke funksjonen or
. Denne funksjonen tar inn to boolske verdier, og gir tilbake True
hvis en, eller begge, verdiene er True
.
a | b | a or b |
---|---|---|
True | False | True |
False | True | True |
False | False | False |
True | True | True |
a = True
b = False
print(f"Kommer jeg til å bli sann eller falsk? {a or b}")
Gruppering av boolske uttrykk
Akkurat som det kan være vanskelig å finne ut hvilken del av et matematisk uttrykk som evalueres først, kan det være vanskelig å vite hvilken del av et boolsk uttrykk som evalueres først. For å gjøre dette tydeligere, kan man bruke parenteser ((
og )
) for å gruppere utrykket.
✍️ Oppgave: Kan du skrive ned sannhetstabellen for uttrykket a and (b or c)
? Det kan være en fordel å bruke en egen kolonne for del-uttrykket b or c
. Kan du skrive litt kode som beregner dette boolske uttrykket?
Samlinger
💡 Læringsmål: I dette kapittelet skal du lære deg å bruke datastrukturer som samler flere elementer til et sett med informasjon.
Datastrukturer regnes som noe av det mest grunnleggende innenfor programmering. Man samler data i en spesifikk struktur, derav selve beskrivelsen. Mye av styrken kommer av mulighetene man har til å utføre bestemte operasjoner på den lagrede dataen, på en veldig effektiv måte; gjør X operasjon på alle elementene i lista Det finnes en rekke datastrukturer hvor alle har egne regler, styrker og begrensninger. I denne korte introduksjonen skal vi se på tre datastrukturer som alle er mye brukt i Python.
Lister
Lister er en samling av elementer i en bestemt rekkefølge. Lister kan inneholde «hva som helst»; strenger, tall, variabler, eller til og med andre datastrukturer. Det er heller ingen krav om at elementene i lista har en viss tilhørighet til de andre elementene. Lister i Python er det man på fagspråket kaller muterbare (på engelsk «mutable»). Elementene i lista kan endres og erstattes, fjernes og nye kan bli lagt til.
En liste defineres på følgende måte:
liste = []
Elementer man legger i lista skilles med komma:
kanaler = ["nrk1", "nrk2", "nrk3"]
Siden lister følger en bestemt rekkefølge kan man hente ut elementer basert på indeks. Lister i Python starter alltid på indeks 0:
print(kanaler[0])
print(kanaler[-1]) # Negativ indeks går «bakover» i lista. -1 vil hente ut det siste elementet
For å modifisere en liste kan man sette elementet på en bestemt indeks til å være noe annet:
kanaler[0] = "nrk11"
For å legge til elementer i en liste kan man bruke følgende operasjoner:
kanaler.append("nrk super") # append() legger til et element i lista. Elementet blir lagt til helt sist i lista
kanaler.insert(1, "nrk tv") # insert() legger til et element på den valgte indeksen.
# Alle andre elmenter på indekser over valgte indeks flyttes «ett steg til høyre»
For å fjerne elementer fra en lista kan man bruke følgende operasjoner:
kanaler.remove("nrk2") # remove() fjerner det første elementet som matcher den valgte verdien
del kanaler[0] # del() fjerner elementet på den valgte indeksen
slettet_kanal = kanaler.pop() # pop() fjerner det siste elementet i lista og lar deg «ta vare på verdien»
# pop(index) fjerner elementet på en bestemt indeks
Tupler
Tupler er også en samling av elementer i en bestemt rekkefølge, men skiller seg fra lister ved å være «immutable». Man kan altså ikke endre elementene, fjerne eller legge til nye.
En tuple defineres slik:
tuple = ()
På samme måte som ved lister skilles elementer ved hjelp av komma:
kanaler = ("nrk1", "nrk2", "nrk3")
For å hente ut elementer fra en tuppel benytter man indeks:
print(kanaler[0])
✍️ Oppgave Lister og tupler
Lag følgende liste og tuppel i en Python-fil:
tv_serier = ["Side om Side", "Nytt på Nytt", "Stjernekamp", "Pørni"]
podcaster = ("Etikketaten", "Kongerekka", "Oppdatert", "Drivkraft")
Bruk det du har lært om lister til å:
- Slette Nytt på nytt ved bruke
del
med riktig indeks - Slette Stjernekamp ved å bruke
remove
- Legge til en ny serie i først i lista
- Legge til en ny serie til sist i lista
Skriv ut lista underveis så du vet hva lista inneholder mellom hvert steg.
Prøv å slette eller en legge til en podcast i tuppelen med podcaster. Hva skjer? Hvorfor?
Oppslagstabeller
Oppslagstabeller, eller dictionaries er en datastruktur som baserer seg på nøkler med bestemte verdier, istedet for en indeks.
En oppslagstabell defineres slik:
dict = {}
Oppslagstabeller benytter også komma for å skille mellom elementene, men legg merke til at hvert element består av en nøkkel med en tilhørende verdi som er skilt med kolon:
kanaler = {"nrk1" : "20. august 1960", "nrk2" : "1. september 1996", "nrk3" : "3. september 2007"}
Man benytter den bestemte nøkkelen for å hente ut tilhørende verdi:
print(kanaler["nrk1"]) # Dette vil skrive ut verdien '20. august 1960'
Men hva skjer dersom man forsøker å hente ut en verdi med en nøkkel som ikke finnes?
print(kanaler["tv2"])
Eksemplet over vil føre til en KeyError fordi nøkkelen ikke finnes i oppslagstabellen.
Man kan bruke operasjonen get()
for å omgå dette problemet:
print(kanaler.get("tv2"))
Dette vil da gi deg «verdien» 'None' i stedet for en error. 'None' betyr at det ikke eksisterer noen verdi og er ikke en error i seg selv, men heller bare for å påpeke at (for oppslagstabeller spesifikt) denne nøkkel-verdi kombinasjonen ikke eksisterer. Dersom man ønsker en spesifikk tilbakemelding og ikke bare 'None' kan man legge til et nytt argument i get() operasjonen:
print(kanaler.get("tv2", "Kanalen du forsøker å hente finnes ikke i oppslagstabellen"))
For å legge til et nytt element i oppslagstabellen definerer man en ny nøkkel og gir den en verdi:
kanaler["nrk super"] = "1. desember 2007"
For å modifisere et bestemt element benytter man også nøkkelen og gir den en ny verdi:
kanaler["nrk3"] = "13. mai 1843" # Historieforfalskning...?
For å slette et element benytter man, ja du gjetter riktig, nøkkelen:
del kanaler["nrk3"]
⚠️ Samlinger kan inneholder andre samlinger! Man kan altså ha en liste av lister, eller oppslagstabeller som inneholder oppslagstabeller. Dette kalles å «neste» - man får da nestede lister eller nestede oppslagstabeller.
✍️ Oppgave Lag en oppslagstabell der hver verdi er en annen samling. Du bestemmer selv hva slags nøkler og samlinger du benytter. F.eks. kan man ta utgangspunkt i eksemplene over der kanal er nøkkel - kanskje verdiene kan være en liste av tv-programmer som sendes på kanalene?
Innebygde funksjoner for samlinger
Python har en rekke innebygde funksjoner som kan benyttes for å utføre diverse handlinger på samlinger. Det er verdt å merke seg at en streng også er en type samling, så de fleste av funksjonene under fungerer også for strenger.
Funksjonen list()
kan benyttes for å lage en liste av f.eks, en streng. Hvert tegn i strengen blir et element i lista.
kanal = "NRK1"
kanal_ord_liste = list(kanal)
print(kanal_ord_liste)
# ['N', 'R', 'K', '1']
Funksjonen sorted()
benyttes for å sortere en samling, for eksempel en liste av tall eller strenger.
episoder_for_planlegging = [1, 2, 8, 5, 10, 4, 22, 11]
sorterte_episoder = sorted(episoder_for_planlegging)
print(sorterte_episoder)
# [1, 2, 4, 5, 8, 10, 11, 22]
Funksjonen reverse()
benyttes for å reversere en gitt sekvens eller samling
episoder_for_planlegging = [1, 2, 8, 5, 10, 4, 22, 11]
episoder_for_planlegging.reverse()
print(episoder_for_planlegging)
# [11, 22, 4, 10, 5, 8, 2, 1]
Funksjonene min()
, max()
og len()
returnerer henholdsvis den minste og største tilfellet i en samling, og lengden av samlingen.
episoder_for_planlegging = [1, 2, 8, 5, 10, 4, 22, 11]
print(min(episoder_for_planlegging))
print(max(episoder_for_planlegging))
print(len(episoder_for_planlegging))
# 1
# 22
# 8
✍️ Oppgave Samle inn postnumrene til de som sitter rundt deg, og legg de inn i en samling. Bruk min
og max
til å finne høyeste og laveste postnummer.
For å vite om et element er inneholdt i en samling kan man bruke in
, vi skal se at dette nøkkelordet vil være nyttig i flere sammenhenger, blant annet når vi kommer til løkker.
tv_serier = ["Side om Side", "Nytt på Nytt", "Stjernekamp", "Pørni"]
inneholder_pompel_og_pilt= "Pompel og Pilt" in tv_serier
print(inneholder_pompel_og_pilt)
✍️ Oppgave Bruk in
til å teste ut om Pørni
er del av lista tv_serier
. Hva med pørni
?
Vi har alt sett at man kan hente ut et bestemt element på en gitt indeks, for eksempel vil tv_serier[1]
gi Nytt på Nytt
. Python har også veldig nyttig funksjonalitet for å hente ut en del av en samling med det som kalles "slicing" på godt norsk. Prinsippet er at man i stedet for tv_serier[index]
kan angi flere ting i klammeparentesene, tv_serier[start:stopp:steg]
. Start er indeksen man vil starte fra, stopp er indeksen man vi slutte ved, merk at selve stopp indeksen ikke er inkludert. Steg lar deg justere om du vil ha hvert element fra start til stopp, annethvert element, hvert tredje element etc. Default-verdien på steg er 1, så steg angis ikke med mindre man skal ha noe annet enn 1.
La oss se på et eksempel med en streng, det vil fungere tilsvarende for andre samlinger. Unntaket er oppslagstabeller som ikke støtter oppsplitting på denne måten.
nrk_plakat_paragraf_12 = "NRK skal ha som formål å oppfylle demokratiske, sosiale og kulturelle behov i samfunnet."
Eksperimenter med linjene under, og se hva som skrives ut om du justerer på indeksene.
print(f"'{nrk_plakat_paragraf_12[16:22]}'") # Henter ut elementer fra og med indeks 16 til, men uten, indeks 22, skriver ut "formål"
print(f"'{nrk_plakat_paragraf_12[:3]}'") # Hvis man vi hente elementer fra starten kan man droppe å skrive 0, [:3] er det samme som [0:3], skriver ut NRK
print(f"'{nrk_plakat_paragraf_12[78:]}'") # Det samme gjelder for slutten, hvis du vil ha med alle elementer på slutten trenger man ikke ha med sluttindeksen. Skriver ut "samfunnet."
print(f"'{nrk_plakat_paragraf_12[-10:]}'") # Det er upraktisk å telle helt til indeks 78. Ved å bruke negativ index i starten får man de x siste elementene, i dette tilfellet de 10 siste bokstavene. "samfunnet." skrives ut
print(f"'{nrk_plakat_paragraf_12[:-10]}'") # Hvis man har negativ indeks i slutten vil man ikke skrive ut de x siste tegnene. Her skrives alt unntatt "samfunnet." ut.
✍️ Oppgave Ta utgangspunkt i lista tall
, og bruk slicing til å lage listene partall
og oddetall
, som skal inneholde henholdsvis bare partallene og oddetallene fra tall
.
tall = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
Input
💡 Læringsmål: I dette kapittelet vil du lære å ta inn data fra brukeren.
Fram til nå har vi skrevet tekst fra programmet til terminalen. Men hvis du har villet endre hvilke verdier programmet ditt opererte på, så har du måttet gå inn i koden og gjøre endringene der.
Hadde det ikke vært mer praktisk om du kunne latt koden være den samme, og heller spurt brukeren om hvilke verdier du skal bruke? Da kan du kjøre programmet ditt flere ganger, men brukt ulike verdier hver gang.
Funksjonen input()
stopper programmet ditt,
og venter på at brukeren som sitter ved terminalen skriver inn ei linje med tekst.
Først når brukeren trykker [ENTER]
vil programmet ditt fortsette.
Resultatet til input()
er teksten som brukeren skrev,
så hvis du vil ta vare på teksten må du tilegne den til en variabel.
Her er et program som bruker print()
til å skrive til terminalen,
etterfulgt av input()
for å lese fra terminalen:
# hilsen.py
print("Hei! Hva heter du?")
navn = input()
print(f"Så hyggelig å hilse på deg, {navn}!")
Når du kjører dette i terminalen, kan det se sånn her ut:
kurs $> python hilsen.py
Hei! Hva heter du?
Vibeke
Så hyggelig å hilse på deg, Vibeke!
✍️ Oppgave:
Eksperimenter med å kjøre eksemplet ovenfor i terminalen.
Hva skjer når du skriver inn mange mellomrom før og etter navnet?
Hva skjer hvis du ikke skriver inn noe navn før du trykker [ENTER]
?
Snarvei for å stille spørsmålet og hente svaret samtidig
Det finnes en snarvei du kan bruke for å stille spørsmålet som brukeren
skal svare på i samme slengen som du henter svaret: input(prompt)
.
Teksten du sender inn vil bli skrevet til terminalen (likt som med print
),
men den vil ikke avslutte med linjeskift.
Resultatet er at når brukeren begynner å skrive,
vil svaret stå på samme linje som spørsmålet.
Grunnen til at du helst bør bruke en prompt,
er at det lar brukerne av programmet ditt vite at programmet venter på dem.
Det trenger ikke alltid være så åpenbart hvorvidt programmet bare har tatt seg en tenkepause,
eller om det venter på brukeren.
For terminalprogram er det derfor en innarbeidet konvensjon om at hvis du skal ta inn noe fra brukeren,
skal du indikere det ved å la markøren stå på samme linje som spørsmålet,
eller starte linja med >
(altså «større enn»-tegnet etterfulgt av mellomrom).
Her er samme eksemplet som ovenfor, gjort om til å stille spørsmålet på samme linje som brukeren skal skrive svaret:
# hilsen_med_prompt.py
navn = input("Hei! Hva heter du? ")
print(f"Så hyggelig å hilse på deg, {navn}!")
Når du kjører dette i terminalen, kan det se sånn her ut:
kurs $> python hilsen_med_prompt.py
Hei! Hva heter du? Vibeke
Så hyggelig å hilse på deg, Vibeke!
✍️ Oppgave: Hvorfor er det et ekstra mellomrom etter spørsmålstegnet i eksemplet ovenfor? Hva skjer hvis du fjerner det?
Lese inn andre typer enn streng
Hittil har vi kun lest inn strenger med input()
.
Og det er i grunn det eneste input()
kan gi oss: strenger.
La oss si at vi vil lese inn brukerens alder, i tillegg til navn. Her er første utkast:
# input_alder_feil.py
print("Hva heter du?")
navn = input("> ")
print(f"Hei, {navn}! Hvor mange år er du?")
alder = input("> ")
print(f"Ok, så du er {alder} år gammel. ")
print("Klarer vi regne ut hvor gammel du vil være om et år?")
neste_alder = alder + 1
print(f"Du vil være {neste_alder} år om et år!")
✍️ Oppgave: Skriv av eksemplet ovenfor og kjør det i terminalen. Hva skjer? Kan du se hvorfor?
Hvis du vil lese inn noe annet enn tekst fra brukeren, for eksempel et tall,
så må du selv gjøre jobben med å konvertere brukerens svar til den riktige datatypen.
For heltall kan du gjøre det med int(x)
.
Her er en fungerende versjon av eksemplet ovenfor:
# input_alder_fikset.py
print("Hva heter du?")
navn = input("> ")
print(f"Hei, {navn}! Hvor mange år er du?")
rå_alder = input("> ")
print(f"Ok, så du er {rå_alder} år gammel. ")
print("Klarer vi regne ut hvor gammel du vil være om et år?")
konvertert_alder = int(rå_alder)
neste_alder = konvertert_alder + 1
print(f"Du vil være {neste_alder} år om et år!")
Kjører du dette i terminalen, kan de se sånn ut:
kurs $> python input_alder_fikset.py
Hva heter du?
> Vibeke
Hei, Vibeke! Hvor mange år er du?
> 54
Ok, så du er 54 år gammel.
Klarer vi regne ut hvor gammel du vil være om et år?
Du vil være 55 år om et år!
✍️ Oppgave: Skriv av eksemplet ovenfor og test det ut i terminalen. Hva skjer hvis du leker at du er en tullebukk som skriver inn tullesvar? Gjør programmet noen antakelser om hva brukeren skriver?
I tillegg til int(x)
kan du også konvertere til flyttall med float(x)
.
Stille ja/nei-spørsmål
Du kan ofte ha behov for å stille kontrollspørsmål til brukeren. Det kan for eksempel være at du vil spørre om bekreftelse før du overskriver ei fil.
Hvorfor ikke bool(x)
?
Ovenfor så vi at du kan bruke int(x)
og float(x)
til å konvertere en streng til typen int
eller float
.
Hva så med boolske verdier — kan vi konvertere dem med bool(x)
?
Vi kan teste det ut i en interaktiv Python-sesjon:
kurs $> python
Python 3.10.9 (main, Dec 7 2022, 01:12:00) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> bool("True")
True
>>> bool("yes")
True
>>> bool("y")
True
>>> bool("ja")
True
>>>
Så langt ser jo dette lovende ut. Hva så med nei?
>>> bool("nei")
True
>>> bool("no")
True
>>> bool("False")
True
>>> bool("")
False
Oi, dette gikk visst ikke så bra.
Det eneste som ble konvertert til False
var en tom streng.
Hva er det som skjer?
✍️ Oppgave:
Åpne en interaktiv Python-sesjon ved å kjøre python
uten noe filnavn etter.
Klarer du finne et mønster i hvordan strenger blir konvertert til en boolsk verdi før du leser fasiten nedenfor?
Hvordan blir strenger konvertert til boolske verdier?
Alle objekter har en implisitt boolsk verdi,
med ulike regler for ulike typer.
For strenger så er denne implisitte boolske verdien True
,
bortsett fra for tomme strenger — de er False
.
Når du kaller bool(x)
,
får du den implisitte verdien til objektet som du sender inn.
Derfor blir "False"
tolket til True
:
Det er en ikke-tom streng, så da er den True
.
Eksempel på problemløsing: Tolke ja/nei-svar
Hvis vi skal få til å tolke svaret på ja/nei-spørsmål, må vi i grunn gjøre en god del arbeid. La oss starte med å lage en liste med krav:
-
Programmet skal spørre brukeren om hen vil fortsette.
-
Brukeren sitt svar skal gjøres om til en boolsk verdi på denne måten:
"y"
,"Y"
,"Yes"
og"yes"
skal tolkes somTrue
"n"
,"N"
,"No"
og"no"
skal tolkes somFalse
- Hvis du bare trykker
[ENTER]
skal du bruke en forvalgt verdi, herTrue
-
Programmet skal skrive den boolske verdien til terminalen.
Først: La oss lage oss et skjelett
Fokuset vårt er på krav 2 ovenfor, men la oss lage alt «rundt» først:
# input_ja_nei_v0.py
print("La oss late som at programmet ønsker å opprette ei fil.")
vil_fortsette = input("Vil du fortsette (Y/n)? ")
vil_fortsette_tolket = vil_fortsette # TODO: Må konvertere til bool
print(f"{vil_fortsette_tolket=}")
I spørsmålet har vi skrevet (Y/n)
.
Dette kommuniserer to ting til brukeren:
- Vi stiller et yes/no-spørsmål
- Hvis du bare trykker
[ENTER]
, vil programmet anta svaret er"yes"
Hvis vi derimot hadde skrevet (y/N)
ville vi kommunisert at forvalget hadde vært "no"
.
(Legg merke til hvilken bokstav som er stor.)
En annen teknikk vi har brukt, er å sette likhetstegn etter uttrykket som vi ønsker å vise frem i f-strengen:
f"{vil_fortsette_tolket=}"
Dette er en snarvei som vil vise både uttrykket du har brukt og
resultatet av uttrykket.
For eksempel vil det stå vil_fortsette_tolket=True
hvis vil_fortsette_tolket
er True
.
✍️ Vil du prøve selv?
Skriv av input_ja_nei_v0.py
ovenfor.
Har du lyst til å prøve deg på å endre definisjonen av vil_fortsette_tolket
sånn at den blir True
eller False
etter reglene ovenfor?
Du kan for eksempel starte med én regel og se at den blir riktig, før du prøver deg på neste regel – likt hvordan du løser én side av en Rubiks kube først.
Hvis du merker at du er litt lite inspirert akkurat nå, kan du bare hoppe over oppgaven – resten av dette kapittelet er dedikert til hvordan den kan løses.
Runde 1: Godta kun y som «ja»
Kravlista ovenfor er ganske lang…
I stedet for å skulle løse alt på én gang,
er det enklere å bare starte med én bit.
La oss starte med å bare godta "y"
for True
, og anta alt annet som False
.
# input_ja_nei_v1.py
print("La oss late som at programmet ønsker å opprette ei fil.")
vil_fortsette = input("Vil du fortsette (Y/n)? ")
vil_fortsette_tolket = vil_fortsette == "y"
print(f"{vil_fortsette_tolket=}")
Hvis du prøvekjører dette programmet i terminalen, vil du fort nok oppdage tre feil:
"Y"
(en stor y) blir tolket somFalse
"yes"
blir tolket somFalse
- Hvis du bare trykker
[ENTER]
får duFalse
, selv om forvalget erTrue
.
La oss prøve å løse det første problemet først.
Runde 2: Ignorer store/små bokstaver
Hvis vi konverterer til minuskler (små bokstaver) først, kan vi beholde sammenlikningen som den er.
# input_ja_nei_v2.py
print("La oss late som at programmet ønsker å opprette ei fil.")
vil_fortsette = input("Vil du fortsette (Y/n)? ")
# Ignorer forskjellen på store og små bokstaver ved å
# normalisere til små bokstaver
vil_fortsette_minuskler = vil_fortsette.lower()
vil_fortsette_tolket = vil_fortsette_minuskler == "y"
print(f"{vil_fortsette_tolket=}")
Nå gjenstår kun to problemer.
La oss gå løs på problemet med at "yes"
blir tolket som False
.
Runde 3: Godta yes som «ja»
# input_ja_nei_v3.py
print("La oss late som at programmet ønsker å opprette ei fil.")
vil_fortsette = input("Vil du fortsette (Y/n)? ")
vil_fortsette_minuskler = vil_fortsette.lower()
# Ignorer alle bokstaver etter den første
vil_fortsette_forbokstav = vil_fortsette_minuskler[0]
vil_fortsette_tolket = vil_fortsette_forbokstav == "y"
print(f"{vil_fortsette_tolket=}")
Vi sammenlikner fortsatt bare med "y"
,
men det kan vi gjøre siden vi bare henter ut den første bokstaven brukeren skrev.
Vi har dog introdusert et nytt problem, for hva skjer hvis du ikke skriver noe?
kurs $> python input_ja_nei_v3.py
La oss late som at programmet ønsker å opprette ei fil.
Vil du fortsette (Y/n)?
Traceback (most recent call last):
File "input_ja_nei_v3.py", line 4, in <module>
vil_fortsette_forbokstav = vil_fortsette_minuskler[0]
IndexError: string index out of range
Når vi gjør vil_fortsette_minuskler[0]
,
antar vi at det er minst ett tegn i strengen.
Den antakelsen holder ikke når du ikke skriver inn noe før du trykker [ENTER]
.
Runde 4: Ikke krasj ved tom verdi
Kan vi tilpasse koden så den ikke gjør noen antakelser om strengen, men heller aksepterer at den kan være tom?
Her kan vi utnytte det faktum at en slice godtar en slutt forbi slutten på strengen. Du kan eksperimentere selv i en interaktiv Python-sesjon:
kurs $> python
Python 3.10.9 (main, Dec 7 2022, 01:12:00) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> "yes"[0]
'y'
>>> ""[0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range
>>> "yes"[0:1]
'y'
>>> ""[0:1]
''
Hvis vi bruker denne nyvunne kunnskapen i eksemplet vårt, kan det bli seende sånn her ut:
# input_ja_nei_v4.py
print("La oss late som at programmet ønsker å opprette ei fil.")
vil_fortsette = input("Vil du fortsette (Y/n)? ")
vil_fortsette_minuskler = vil_fortsette.lower()
# Fortsett med å ignorere alle tegn etter det første,
# men la en tom streng forbli tom
vil_fortsette_forbokstav = vil_fortsette_minuskler[0:1]
vil_fortsette_tolket = vil_fortsette_forbokstav == "y"
print(f"{vil_fortsette_tolket=}")
Legg merke til at [0:1]
kan forkortes til [:1]
, siden starten er 0
.
vil_fortsette_forbokstav
vil bli satt til det første tegnet hvis det finnes,
ellers en tom streng.
Det siste problemet som gjenstår er å få True
som forvalg.
Runde 5: Godta tom verdi som «ja»
# input_ja_nei_v5.py
print("La oss late som at programmet ønsker å opprette ei fil.")
vil_fortsette = input("Vil du fortsette (Y/n)? ")
vil_fortsette_minuskler = vil_fortsette.lower()
vil_fortsette_forbokstav = vil_fortsette_minuskler[:1]
# Aksepter tom streng, i tillegg til "y", som sann
vil_fortsette_tolket = (
vil_fortsette_forbokstav == "y"
or vil_fortsette_forbokstav == ""
)
print(f"{vil_fortsette_tolket=}")
Det er fortsatt en svakhet her: Du kan skrive hva som helst, og det vil bli tolket som «nei» så lenge det ikke starter på bokstaven y. Vi kunne valgt å kjefte på brukeren hvis de gjorde noe så tullete, men for enkelthetens skyld antar vi heller at brukeren ikke vil fortsette.
Runde 6: Trekk sammen til ei linje
Nå har vi gjort én ting av gangen i dette eksemplet, men du kan ta noen snarveier og få til det samme i færre kodelinjer:
# input_ja_nei_v6.py
print("La oss late som at programmet ønsker å opprette ei fil.")
vil_fortsette = input("Vil du fortsette (Y/n)? ").lower()[:1] in ("y", "")
print(f"{vil_fortsette=}")
Her har vi gjort alle mellomstegene samtidig.
Vi har også brukt in
-operatoren til å sjekke om uttrykket på venstre hånd
(den første bokstaven til brukeren som minuskel)
er å finne i tuplen av verdier på høyre hånd ("y"
eller ""
).
Programmet oppfører seg helt likt som versjon 5, det er bare litt vanskeligere å lese, men desto kortere. Balansegangen mellom «lettforståelig, men langt» og «kort, men vanskelig å lese» kan være vanskelig å få rett. I dette tilfellet hadde nok den beste løsninga vært å bruke den lange varianten, men gjemt den bort som en gjenbrukbar funksjon. Da tar den liten plass der hvor du spør brukeren om hen vil fortsette, samtidig som du kan gå til den lange versjonen når du trenger å tilpasse den eller se hvordan den funker. Du lærer deg hvordan du kan lage slike funksjoner helt i slutten av denne delen.
Når du kjører eksemplet, kan det se sånn her ut:
kurs $> python input_ja_nei_v6.py
La oss late som at programmet ønsker å opprette ei fil.
Vil du fortsette (Y/n)? No
vil_fortsette=False
✍️ Oppgave:
La oss si at vi ønsker å slette ei fil i stedet.
Da bør jo forvalget være False
, for å unngå uhell.
Klarer du tilpasse input_ja_nei_v5.py
sånn at den antar False
når brukeren bare trykker [ENTER]
uten å skrive noe?
✍️ Oppgave:
I eksemplet ovenfor antok vi at brukeren svarte på engelsk.
Klarer du tilpasse input_ja_nei_v5.py
sånn at brukeren kan svare på norsk (ja/nei)?
Det er en ganske åpenbar bit som mangler i eksemplet vi har bygd ut her: Det spiller ingen rolle om du svarer ja eller nei. Det vil så klart skrives ulike ting til terminalen, men programmet vil utføre de samme instruksjonene uansett hva du svarer. I neste kapittel skal vi se på hvordan programmet kan gjøre ulike ting avhengig av hva du svarer.
Hvis, omatte og ellers
💡 Læringsmål: I dette kapittelet skal du lære deg å skrive kode som gjør valg.
Hittil har programmene våre kjørt fra topp til bunn: Så snart det har gjort seg ferdig med én instruks, har programmet fortsatt med neste. Det øyeblikket du begynner å få spesialtilfeller som skal behandles på forskjellige måter, får du behov for å variere hva koden gjør.
De aller fleste programmeringsspråk har det som kalles for betingelser (conditionals). De kan sammenliknes med flytdiagram som du kanskje har støtt på i andre tilfeller:
flowchart TD Start((Start)) Hvis{Er betingelsen sann?} Sann[Kode som skal kjøres <br/>når betingelsen er sann] Usann[Kode som skal kjøres <br/>når betingelsen er usann] Slutt((Slutt)) Start-->Hvis Hvis -- Ja --> Sann --> Slutt Hvis -- Nei --> Usann --> Slutt
Her er en illustrasjon på hvordan betingelser ser ut i Python:
# illustrasjon_if_else.py
print("Start")
if 2 + 2 == 4:
print("Denne koden kjøres hvis betingelsen er sann")
print("Du kan ha flere kodelinjer")
else:
print("Denne koden kjøres hvis betingelsen er usann")
print("Du kan ha flere kodelinjer her også")
print("Slutt")
Eksempel på kjøring:
kurs $> python illustrasjon_if_else.py
Start
Denne koden kjøres hvis betingelsen er sann
Du kan ha flere kodelinjer
Slutt
Hvordan klarer Python å skille mellom koden som skal kjøre avhengig av betingelsen, og resten av koden? Svaret er kodeblokker. Ei kodeblokk er ei samling med kode som hører sammen og blir eksekvert sammen. I Python bruker vi et kolon på slutten av ei linje til å indikere at «her kommer ei kodeblokk!». Hver linje som inngår i kodeblokken må ha et større innrykk enn koden rundt, for eksempel fire mellomrom. Den første linja som har mindre innrykk avslutter kodeblokken og vil ikke inngå i den. (Blanke linjer er tillatt.)
Innrykk er viktig i Python
Du vil som oftest få hjelp av editoren din,
for eksempel vil den legge på innrykk når du skriver if:[ENTER]
.
Utenom automatikken kan du som oftest bruke [TAB]
-tasten til å lage et passelig stort innrykk,
men husk på at en tabulator er noe annet enn et mellomrom.
Mange editorer vil sette inn mellomrom når du trykker [TAB]
i stedet for å sette inn et tabulator-tegn,
men hvis den ikke gjør det og du blander tabulator med mellomrom får du trøbbel:
# feil_innrykk.py
if 2 + 2 == 4:
print("Hei")
print("Hallo")
Du kan ikke se det med det blotte øye, men her har vi brukt mellomrom på «Hei» og tabulator på «Hallo». Prøver du å kjøre dette, får du feil:
kurs $> python feil_innrykk.py
File "/home/n123456/kurs/feil_innrykk.py", line 3
print("Hallo")
TabError: inconsistent use of tabs and spaces in indentation
Det finnes en måte du kan åpne opp øynene dine for ulike typer mellomrom. I Visual Studio Code kan du velge View, Appearance og Render Whitespace. Vanlig mellomrom blir vist som prikker, mens en tabulator blir vist som ei pil. Feilen ovenfor løser du ved å bare bruke det ene eller det andre, aldri en blanding.
I Visual Studio Code kan du velge hva slags innrykk den skal bruke. Du må se nederst til høyre i vinduet, der hvor det står «Ln #, Col #» (med tall i stedet for #). Dette er linja og kolonna markøren din står i. Etter denne står det hva slags innrykk editoren bruker, for eksempel «Spaces: 4». Trykk på denne for å endre typen innrykk og størrelsen på innrykket.
Det er utkjempet kriger på internett over hva som er riktig av mellomrom og tabulator, så vi nøyer oss med å si at mellomrom gjør at koden ser lik ut for alle, mens tabulator lar ulike personer justere hvor mange «mellomrom» en tabulator skal tilsvare visuelt. Det viktigste er at du bruker én av de konsekvent i hele prosjektet; hvilken du bruker er underordnet.
Du kan droppe else
Noen ganger har du ikke noe du vil gjøre i else
.
Da kan du bare droppe den, og ha en if
med tilhørende kodeblokk.
Som flytdiagram:
flowchart TD Start((Start)) Hvis{Er betingelsen sann?} Sann[Kode som skal kjøres <br/>når betingelsen er sann] Slutt((Slutt)) Start-->Hvis Hvis -- Ja --> Sann --> Slutt Hvis -- Nei --> Slutt
Som Python-kode:
# illustrasjon_if.py
print("Start")
if 2 + 2 == 5:
print("Denne koden kjøres hvis betingelsen er sann")
print("Du kan ha flere kodelinjer")
print("Slutt")
Eksempel på kjøring:
kurs $> python illustrasjon_if.py
Start
Slutt
Eksempel: Hilsen
Vi kan bygge videre på hilsenen vi lagde oss i forrige seksjon. Klarer vi å reagere på det brukeren skriver?
# hilsen_med_if.py
navn = input("Hei! Hva heter du? ")
if navn == "Vibeke Fürst Haugen":
print("Oi! God dag, ærede kringkastingssjef!")
print("Hva kan jeg hjelpe deg med i dag?")
else:
if navn:
print(f"Hyggelig å hilse på deg, {navn}!")
else:
print("Feil: Du må oppgi et navn")
print("Takk for nå!")
Her har vi brukt if
og else
inni else
.
Det går helt fint an å kombinere dem på denne måten,
i så mange lag bortover som du ønsker.
Hvor praktisk det blir, er et annet spørsmål...
Eksempel på kjøring:
kurs $> python hilsen_med_if.py
Hei! Hva heter du? Vibeke Fürst Haugen
Oi! God dag, ærede kringkastingssjef!
Hva kan jeg hjelpe deg med i dag?
Takk for nå!
kurs $> python hilsen_med_if.py
Hei! Hva heter du?
Feil: Du må oppgi et navn
Takk for nå!
kurs $> python hilsen_med_if.py
Hei! Hva heter du? Thorben
Hyggelig å hilse på deg, Thorben!
Takk for nå!
Hvordan ville dette sett ut som flytdiagram?
flowchart TD Start((Start)) Input["navn = input('Hei! Hva heter du? ')"] If{Er navn == 'Vibeke Fürst Haugen' sant?} PrintHeiKsjef["print('Oi! God dag, ærede kringkastingssjef!')"] PrintHvaKanJegHjelpeDegMed["print('Hva kan jeg hjelpe deg med i dag?')"] Elif{Er navn en ikke-tom streng?} PrintHilsen["print(f'Hyggelig å hilse på deg, {navn}!')"] PrintFeil["print('Feil: Du må oppgi et navn')"] PrintTakk["print('Takk for nå!')"] Slutt((Slutt)) Start-->Input-->If If -- Ja --> PrintHeiKsjef --> PrintHvaKanJegHjelpeDegMed --> PrintTakk If -- Nei --> Elif Elif -- Ja --> PrintHilsen --> PrintTakk Elif -- Nei --> PrintFeil --> PrintTakk PrintTakk --> Slutt
Snarvei: Kombinere else
og if
Python har en snarvei du kan bruke til å kombinere else
og if
.
Denne snarveien heter selvfølgelig elif
.
I forrige seksjon hadde vi en if
inne i kodeblokken som hørte til en else
.
Det går kanskje greit når du bare har én sånn if/else-struktur inni i en annen,
men du får fort en veldig lang venstremargin når du får tre eller flere spesialtilfeller.
For å konvertere det forrige eksemplet til å bruke elif
,
kan du trekke sammen else:
med if:
og
redusere innrykket med ett hakk:
# hilsen_med_elif.py
navn = input("Hei! Hva heter du? ")
if navn == "Vibeke Fürst Haugen":
print("Oi! God dag, ærede kringkastingssjef!")
print("Hva kan jeg hjelpe deg med i dag?")
elif navn:
print(f"Hyggelig å hilse på deg, {navn}!")
else:
print("Feil: Du må oppgi et navn")
print("Takk for nå!")
Koden oppfører seg helt likt som før, den har bare blitt litt lettere å forholde seg til.
Eksempel: Avslutte programmet tidlig
Nå som vi kan gjøre forskjellige ting avhengig av hva brukeren skriver, kan vi også få til å gjøre noe bare hvis brukeren ønsker det. For eksempel kan vi avslutte programmet vårt tidlig hvis brukeren ikke ønsker å fortsette.
For å få til å avslutte programmet tidlig, må vi importere en modul, i tilfellet her sys
.
Ved å importere sys
får vi tilgang til alle funksjonene som ligger i sys
-modulen.
En av disse funksjonene er sys.exit()
, som kan brukes til å avslutte programmet.
Hvis du sender en streng inn til sys.exit(arg)
vil strengen printes til konsoll,
før programmet avsluttes med feilkode 1
.
# prompt_fortsett.py
import sys
print("La oss late som at programmet ønsker å slette ei fil.")
vil_fortsette = input("Vil du fortsette (y/N)? ").lower()[:1] == "y"
if not vil_fortsette:
sys.exit("Avslutter...")
print("Sletter fila...")
Her har du et eksempel på if
uten elif
eller else
.
Vanligvis ville programmet ha fortsatt til linja print("Sletter fila...")
uansett,
men sys.exit
setter en stopper for det.
En mer naiv løsning hadde vært:
if vil_fortsette:
print("Sletter fila...")
else:
print("Avslutter")
Svakheten med dette alternativet er at det blir uoversiktlig hvis det er
hundrevis av ting som må gjøres mellom if
og else
.
Da blir det ryddigere hvis du heller bare rydder ut av veien tilfellet hvor brukeren vil avbryte,
og kan skrive resten av programmet uten innrykk.
Dette er en vanlig teknikk for å unngå at det blir for mange innrykk til slutt.
Du kan se for deg hvor langt inn du måtte ha rykket koden hvis du skulle spurt om
brukeren vil fortsette et par-tre ganger til.
Eksempel på kjøring:
kurs $> python prompt_fortsett.py
La oss late som at programmet ønsker å slette ei fil.
Vil du fortsette (y/N)?
Avslutter...
kurs $> python prompt_fortsett.py
La oss late som at programmet ønsker å slette ei fil.
Vil du fortsette (y/N)? yes
Sletter fila...
Eksempel: Bruke forvalgt verdi for input()
Si at du vil ha tak i brukerens navn, men vil falle tilbake på brukernavnet hvis brukeren ikke skriver noe. Da kan du først lage en variabel med navnet du vil falle tilbake på, og så spørre brukeren om navnet. Hvis brukeren oppga et navn kan vi overskrive variabelen som vi allerede skrev et navn til, men hvis brukeren ikke oppga noenting, kan vi bare la variabelen være som den er.
For å hente brukernavnet til den innloggede brukeren,
kan vi importere modulen getpass
.
Den har en funksjon kalt getpass.getuser()
som forsøker å hente brukernavnet fra systemet.
import getpass
# Bruk brukernavnet som forvalg
navn = getpass.getuser()
oppgitt_navn = input(f"Navn: [{navn}] ").strip()
if oppgitt_navn:
navn = oppgitt_navn
print(f"Hei, {navn}")
En fordel med denne løsninga er at du kan stole på at navn
har en verdi,
uansett hva brukeren gjør.
PS: Her brukte vi str.strip()
til å fjerne mellomrom fra starten og slutten på strengen.
En bieffekt av dette er at du vil få en tom streng, selv hvis du skriver inn mange mellomrom.
På den måten får vi falt tilbake til brukernavnet i tilfellet hvor vi ellers ville brukt strengen
(siden betingelsen if oppgitt_navn
ville vært oppfylt når oppgitt_navn == " "
, med andre ord en ikke-tom streng).
Tilegne ulik verdi basert på boolsk uttrykk
Det finnes en snarvei du kan bruke alle de gangene du har en variabel som enten skal være det ene eller det andre. For eksempel når du vil bruke riktig av entall og flertall, og ikke vet på forhånd hvor mange det er snakk om.
Formatet er:
<verdi hvis sann> if <betingelse> else <verdi hvis usann>
Du kan selvfølgelig skrive noe sånt som «Endret 3 fil(er)» og ta høyde for entall og flertall på den måten. Men du kan også velge å være perfeksjonist:
# entall_flertall.py
antall_filer = int(input("Hvor mange filer vil du endre? "))
# Vi endrer ingenting, det er bare på liksom
fil_substantiv = "fil" if antall_filer == 1 else "filer"
print(f"Endret {antall_filer} {fil_substantiv}")
Eksempel på kjøring:
kode $> python entall_flertall.py
Hvor mange filer vil du endre? 9000
Endret 9000 filer
kode $> python entall_flertall.py
Hvor mange filer vil du endre? 1
Endret 1 fil
Løkker
💡 Læringsmål: I dette kapittelet skal du lære deg å bruke løkker for å gjøre ting flere ganger.
Løkker lar deg gjøre handlinger og operasjoner flere ganger, for eksempel ved å itere over alle elementene i en samling og slette elementene som matcher et spesifikt kriterie. Vi skal se på to typer løkker, nemlig for-løkker og while-løkker.
for-løkker
For-løkker benyttes vanligvis for å itere over en gitt samling for å utføre handlinger og operasjoner for hvert enkelt element. Løkka utfører altså én handling for hvert element i lista, uavhengig av hvor mange elementer som finnes i lista. Variabelen for hvert enkelt element endres ved hver iterasjon, og eksisterer kun i «scopet» til for-løkka. I eksemplet under benytter vi kanal-lista vi tidligere så på, og hvis denne inneholder NRK1, NRK2, og NRK3 vil løkka kjøre tre ganger. Ved første iterasjon vil variabelen 'kanal' være "nrk1", neste iterasjon "nrk2" og i siste iterasjon "nrk3".
En for-løkke kan altså defineres slik:
for kanal in kanaler:
print(kanal) # Gjør noe med elementent, eller utfør andre handlinger
✍️ Oppgave 1
Her en liste over alle fornavnene til de andre i avdelingen din.
navneliste = ["Vibeke", "Aisha", "Carlos", "Vibeke", "Lise", "Fatima", "Per", "Leyla", "Oliver", "Vibeke", "Henrik", "Anna"]
Hvor mange i avdelingen din heter Vibeke? Bruk en for-løkke til å finne det ut. Lag en variabel for å ta vare på antall Vibeke, og skriv ut innholdet i variabelen til sist i programmet.
✍️ Oppgave 2
Lag en quiz med tre spørsmål. Spørsmålene skal være strukturert i en liste der hvert element skal være en oppslagstabell med èn nøkkel for spørsmål
og en annen for svar
. Bruk for-løkka til å iterere over spørsmålene og ta inn svar fra brukeren ved å bruke input
-funksjonen.
Et spørsmål kan se slik ut:
{"spørsmål": "Hva er hovedstaden i Norge?", "svar": "Oslo"}
Ta vare på hvor mange ganger brukeren har svart korrekt og skriv det ut når quizen er ferdig.
Generators
Man kan også benytte «genererings-funksjoner», eller «generators», i Python for å iterere over en en sekvens av verdier, i likhet med samlinger, men da uten at Python lagrer selve sekvensen i minnet slik som med en definert samling. Og siden sekvensen ikke lagres i minnet og heller kun genereres «på sparket» så kan man kun iterere over disse én gang. Fordelen med «generators» er at de er veldig nyttige når man jobber med veldig store mengder data, eller bokstavelig talt uendelige sekvenser.
Funksjonen range()
er et eksempel på en «generator» i Python. Denne genererer en sekvens med nummer og defineres som følgende:
range(start, stop, step) # 'start' definerer det første nummeret i sekvensen
# 'stop' definerer det siste nummeret i sekvensen. Merk at nummeret ikke blir inkludert.
# 'step' definerer antallet det skal økes med
Eksemplet under starter altså sekvensen på 2
, går til 10
, men tar bare annenhvert tall.
Resultatet blir altså 2, 4, 6, 8.
for i in range(2, 10, 2):
print(i)
✍️ Oppgave 3 Fra 1947 har vi avholdt kommunevalg hvert fjerde år. Kan du liste opp alle de årene fra da frem til i dag?
while-løkker
While-løkker benyttes for å utføre handlinger og operasjoner så lenge et bestemt kriteriet er gitt. Det betyr at disse løkkene i teorien kan kjøre «for alltid» så lenge kriteriet for hva som skal stoppe løkka ikke gis, og dette må man være forsiktig med.
En while-løkke kan defineres slik:
while kriterie:
# handlinger eller operasjoner som skal utføres
Mer spesifikt:
count = 0
while count < 5:
print(count)
count += 1
Her har vi en variable kalt 'count' som har verdien 0. Denne verdien benyttes for å definere kriteriet til while-løkka; selve løkka skal altså kjøre helt til verdien til 'count' blir 5. I selve løkka øker vi verdien med én i hver iterasjon.
Kontrollere løkker ved å bruke break
Ved å bruke break
stopper vi utførelsen av løkka. I dette eksempelet lar vi brukeren gjette hvilket år NRK ble grunnlagt.
Eksempel: NRK-quiz med break
# Definerer det årstallet som skal gjettes
korrekt_årstall = 1933
while True:
# Tar inn brukerens gjett
gjett = input("Når ble NRK grunnlagt? ")
# Sjekker om inputen er et tall
if gjett.isdigit():
gjett = int(gjett)
# Sjekker om brukerens gjett er riktig
if gjett == korrekt_årstall:
print("Gratulerer! Du gjettet riktig.")
# Stopper utførelsen av løkka og quizen er ferdig.
break
else:
# Gir hint til brukeren
if gjett > korrekt_årstall:
print("Feil. NRK ble grunnlagt før dette. Prøv igjen. \n")
else:
print("Feil. NRK ble grunnlagt etter dette. Prøv igjen. \n")
else:
print("Vennligst oppgi et årstall.")
# Tilbakemelding til brukeren etter at spillet er ferdig
print("Takk for at du spilte!")
Kontrollere løkker ved å bruke flagg
Et flagg er en variabel som signaliserer når en bestemt betingelse er oppfylt. Det kan brukes for å kontrollere flyten av programmet. Vi bruker samme eksempel som ovenfor, men nå ved å bruke flagg i stedet for break.
Eksempel: NRK-quiz med flagg
Vi vil bruke et flagg kalt korrekt_gjett
for å avgjøre om brukeren har gjettet riktig.
# Definerer det årstallet som skal gjettes
korrekt_årstall = 1933
# Initialiserer flagget
korrekt_gjett = False
while not korrekt_gjett:
# Tar inn brukerens gjett
gjett = input("Når ble NRK grunnlagt? ")
# Sjekker om inputen er et tall
if gjett.isdigit():
gjett = int(gjett)
# Sjekker om brukerens gjett er riktig
if gjett == korrekt_årstall:
print("Gratulerer! Du gjettet riktig.")
korrekt_gjett = True
else:
# Gir hint til brukeren
if gjett > korrekt_årstall:
print("Feil. NRK ble grunnlagt før dette. Prøv igjen. \n")
else:
print("Feil. NRK ble grunnlagt etter dette. Prøv igjen. \n")
else:
print("Vennligst oppgi et årstall.")
# Tilbakemelding til brukeren etter at spillet er ferdig
print("Takk for at du spilte!")
Å bruke et flagg på den måte gjør koden mer lesbar ved å klart vise hva som er kriteriet til løkka.
✍️ Oppgave 4: Interaktiv handleliste
Mål: Skriv et program som lar brukeren legge til varer i en handleliste til hen bestemmer seg for å avslutte. Når hen avslutter, skal programmet skrive handlelisten ut til brukeren.
Tips:
- Start med en tom handleliste.
- Bruk en
while
-løkke for å be brukeren legge til varer. - Hvis brukeren skriver "AVSLUTT", skal programmet avsluttes og vise hele listen.
- Hvis brukeren skriver inn noe annet, legg det til i handlelisten.
- Hold styr på antall varer i listen og vis dette antallet før programmet avsluttes.
Forventet interaksjon:
Legg til en vare i handlelisten (eller skriv 'AVSLUTT' for å avslutte): Epler
Legg til en vare i handlelisten (eller skriv 'AVSLUTT' for å avslutte): Melk
Legg til en vare i handlelisten (eller skriv 'AVSLUTT' for å avslutte): Brød
Legg til en vare i handlelisten (eller skriv 'AVSLUTT' for å avslutte): AVSLUTT
Din handleliste:
1. Epler
2. Melk
3. Brød
Totalt 3 varer i handlelisten.
Ekstra: Bruk av continue i løkker
Bruken av continue
i en løkke kan være nyttig når du vil hoppe over en bestemt iterasjon basert på et gitt kriterie, men vil fortsette med de neste iterasjonene.
Eksempel: Skrive ut tall som ikke er delelige med 5 mellom 1 og 20
La oss si at du vil skrive ut tallene fra 1 til 20, men du vil hoppe over de tallene som er delelige med 5. Her kan continue
brukes:
for num in range(1, 21):
if num % 5 == 0: # Sjekker om tallet er delelig med 5
continue
print(num)
Funksjoner
💡 Læringsmål: I dette kapittelet skal du lære å lage funksjoner slik at du kan dele opp koden i mindre biter og kan bruke samme kodebit flere steder.
Hva er en funksjon?
Funksjoner i programmering ligner mistenkelig på funksjoner man lærte om i matematikken på skolen. Som eksempelet i tabellen under viser, tar matematiske funksjoner inn en verdi og gir en verdi tilbake.
x | f(x) = x - 2 |
---|---|
4 | 4 - 2 = 2 |
2 | 2 - 2 = 0 |
0 | 0 - 2 = -2 |
I programmering brukes funksjoner til å abstrahere vekk detaljer slik at man i lange programmer ikke behøver å forholde seg til alle ting hele tiden. I stedet kan man dele koden opp i mindre deler, funksjoner, og man trenger bare å vite navnet på funksjonen og verdiene den eventuelt trenger som input, på det stedet der man vil bruke funksjonen. Det er litt på samme måte som i en matoppskrift, det er ikke alle detaljer som forklares hele tiden. Om det for eksempel står "kok opp 2 liter vann" i oppskriften er det vanligvis ikke forklart hvordan man koker vann.
Funksjoner gjør også at man ikke trenger å gjenta kodelinjer som skal gjøre (nesten) det samme. Det er bedre å ha denne samme funksjonaliteten ett sted i koden. Da er det bare ett sted man må forsikre seg om at funksjonaliteten er kodet riktig, og om man trenger å endre funksjonaliteten senere en gang, er det bare ett sted man trenger å oppdatere.
Funksjoner i Python
En funksjon i Python ser ut på følgende måte:
def lag_hilsen(navn):
hilsen = f"Hei {navn}!"
return hilsen
Første linje i funksjonen består av nøkkelordet def
som angir at definisjonen av funksjonen starter her. Teksten som kommer etterpå er navnet på selve funksjonen, deretter kommer parametrene inni parentesen, før linja avsluttes med :
. Hvis funksjonen ikke skal ta inn noen verdier er det tomt mellom parentesene ()
, hvis funksjonen har flere parametre er de separert med komma (fornavn, etternavn)
. Parametrene brukes som variable inni funksjonen og en parameter vil inneholde verdien som angis når man bruker funksjonen, det som sendes inn som argument til funksjonen.
Selve innholdet i funksjonen kommer på linja etter :
, og alt som skal være inni funksjonen må ha et innrykk. Til sist i funksjonen returneres verdien man vil ha tilbake fra funksjonen ved å skrive return
etterfulgt av det man vil returnere. Funksjoner i Python må ikke ha en eksplisitt returverdi. Om det ikke er noen linje med return
til slutt, vil funksjonen implisitt returnere verdien None
.
Lag en ny Python-fil, f.eks med navn funksjoner.py
, og kopier funksjonen over inn i fila. Deretter kan du i fila kalle funksjonen og lagre resultatet i en variabel, og så printe resultatet:
hilsen = lag_hilsen("Jens")
print(hilsen)
Test ut å kalle funksjonen med ditt eget navn og skriv ut hilsenen til deg selv.
Når man skal sende inn argument til en funksjon kan man eksplisitt navngi parameteren. Det er spesielt nyttig når man har flere argumenter, så man er sikker på at riktig parameter får riktig verdi. I eksempelet kan man derfor skrive:
hilsen = lag_hilsen(navn = "Jens")
I eksempelet er navn
parameter og Jens
argument for funksjonen lag_hilsen
.
✍️ Oppgave: Test å legge til eller endre noe i funksjonen du har i skriptet. Klarer du å endre funksjonen så programmet feiler når du kjører det? Hvorfor feiler det?
✍️ Oppgave: Lag en funksjon som skriver ut hjelp til terminalen
- Lag funksjonen
print_hjelp()
som ikke tar inn noe argument og som ikke returnerer noen verdi. Brukprint()
-funksjonen til å skrive ut en valgfri setning om hjelp til terminal. - Kall funksjonen i programmet, og se at hjelpeteksten skrives ut når programmet kjører.
- Lagre returnverdien fra funksjonen i en variabel, for eksempel
hjelp = print_hjelp()
, og print ut denne variabelen. Hva skrives ut? - Prøv å legge inn en eksplisitt
return None
i slutten av funksjonen, hva skrives ut fra variabelen nå?
✍️ Oppgave: Lag en funksjon som lager bærer fra programkode og type
Tv- og radioprogram har en programkode (eller program-ID) som består av fire bokstaver og åtte tall, for eksempel KMNO10010922
. En type bærer består av to bokstaver, for eksempel AH
, og et program sin bærer består av programkoden til programmet satt sammen med bærertypen, KMNO10010922AH
.
- Lag funksjonen
lag_bærer(programkode, bærertype)
. Denne har to parametere,programkode
ogbærertype
, og skal returnere en streng der programkode og bærertype er satt sammen. - Kall funksjonen i programmet og se at verdien som returneres er som du forventer.
✍️ Oppgave: Lag en funksjon som splitter opp bæreren
- Lag funksjonen
del_opp_bærer(bærer)
som er motsatt avlag_bærer(programkode, bærertype)
. Den skal ta inn en bærer, og dele denne opp iprogramkode
ogbærertype
, og returnere disse to verdiene. Funksjoner kan bare returnere én ting, så en måte å returnere flere ting på er å sette de sammen til et tuppel. - Kall funksjonen i programmet og se at verdien som returneres er som du forventer.
- Prøv å kombinere de to bærer-funksjonene, kall først
lag_bærer
og bruk resultatet herfra som argument tildel_opp_bærer
, og motsatt, kalldel_opp_bærer
og bruk resultatet herfra som argument tillag_bærer
. Hvordan forventer du at disse funksjonene fungerer sammen?
✍️ Oppgave: Rydd opp i programmet
Rydd opp i programmet slik at man unngår at samme funksjonalitet kodes på ulik måte, og ikke har funksjonalitet duplisert i koden. Bruk minst én av bærer-funksjonene over, du kan også lage nye funksjoner der du synes det passer.
program1_kode = "DVFJ60000121"
program1_bærertype = "AH"
program1_bærer = program1_kode + program1_bærertype
program2_kode = "ODRP20002101"
program2_bærertype = "AB"
program2_bærer = "ODRP20002101AB"
program3_kode = program1_kode
program3_bærer = program3_kode + "AA"
programmer = [
{
"kode": program1_kode,
"bærertype": program1_bærertype,
"bærer": program1_bærer
},
{
"kode": program2_kode,
"bærertype": program2_bærertype,
"bærer": program2_bærer
},
{
"kode": program3_kode,
"bærertype": program3_bærer[-2:],
"bærer": program3_bærer
}
]
print(programmer)
Kommandolinjeapplikasjon
💡 Læringsmål: I denne delen av kurset vil du lære å lage en enkel kommandolinjeapplikasjon som leser input fra fil og fra bruker, og gir et fornuftig svar.
Hva skal vi lage?
Vi skal lage en kommandolinjeapplikasjon, som er et langt ord for et program som ikke har et eget grafisk grensesnitt, men som kjører i terminalen. Kommunikasjonen mellom bruker og program foregår via tekst.
Når du er ferdig med dette kapittelet vil du ha laget en kommandolinjeapplikasjon som leser en elektronisk programguide fra en fil på json
-format, og skriver ut til brukeren av programmet en oversikt over hvilke kategorier programmene har, og hvor mange programmer det er av hver kategori.
Plan
Vi trenger å lære flere ting før vi kommer til selve applikasjonen, her er det vi skal lære i denne delen:
Vi leser data fra en fil
💡 Læringsmål: I dette kapittelet lærer du hvordan du leser data fra en fil.
Lag deg en .py
-fil som du vil skrive programmet ditt i, og kopier fila serier.txt til samme mappe som Python-filen.
Det er fila serier.txt
vi skal arbeide med. Hver linje i denne fila inneholder serieId
og tittel
for en tv-serie, separert med ,
. Vi skal lese fila, splitte innholdet og skrive ut alle titlene i fila.
For å lese en fil må vi først åpne fila, deretter kan vi lese ut innholdet. Funksjonen open
brukes for å åpne fila, du kan lese mer om denne funksjonen i Python-dokumentasjonen.
fil = open("serier.txt", "r")
Første argumentet til open
er navnet på fila, det andre argumentet angir hva slags modus fila skal åpnes i, r
står for read
, det vil si lesemodus. Dette er også default-verdien, så r
kan utelates om man kun skal lese fila.
Når vi har åpnet fila kan vi lese ut alt som tekst med funksjonen read
. Legg til linjene under og kjør programmet.
tekst = fil.read()
print(tekst)
Ser du noe rart i det som skrives ut? For eksempel at tittelen til "Fra bølle til bestevenn" ser ut som Fra bølle til bestevenn
. Det er fordi fila leses med feil tegnsett. Det kan vi løse ved å sende tegnsettet vi ønsker som argument til open
, med parameteren encoding
. Endre linja med open
til følgende:
fil = open("serier.txt", "r", encoding="utf-8")
Her sier vi eksplisitt at fila skal enkodes med utf-8
.
Når man har åpnet en fil er det fint å lukke den pent etter seg når man er ferdig med den. Det er to måter å gjøre det på, den ene måten er å ha en linje etter at man er ferdig med filen, som lukker den, fil.close()
. Det vi i stedet vil gjøre er å bruke en with
-blokk. Da vet Python at den skal lukke fila når man går ut av blokken. og vi slipper selv å huske på det, og å finne ut hvor i koden det er lurt å lukke fila. Med with
kan det se slik ut:
with open("serier.txt", "r", encoding="utf-8") as fil:
tekst = fil.read()
print(tekst)
Bytt ut innholdet i programmet ditt med linjene over og se at programmet kjører som før.
Vi leser nå hele filen som en lang streng, men for å oppnå målet vårt med å skrive ut en liste med bare titlene må vi kunne lese hver linje for seg. Det er flere måter å få til det på. Fil har en metode readlines()
, men det som er upraktisk med den er at den beholder linjeskift-tegnet \n
i slutten av hver linje. I stedet bruker vi string sin metode splitlines()
. Vi kan derfor bytte ut innholdet i with-blokka med følgende linjer:
linjer = fil.read().splitlines()
for linje in linjer:
print(linje)
Men fortsatt er vi ikke helt i mål, nå skriver vi ut hele linja, ikke bare tittelen. For å få tak i tittelen kan vi bruke split
-metoden til string, som deler en streng for hver gang den finner det angitte skilletegnet.
✍️ Oppgave: Kan du fullføre programmet slik at det bare skriver ut tittelen?
for linje in linjer:
deler = linje.split(",")
# print(???)
✍️ Oppgave: Finn eller lag en fil med tekstlig innhold, og eksperimenter med å lese fila og skrive ut innholdet i terminalen
Om du trenger litt inspirasjon kan du se om du liker noen av csv-filene på denne siden.
Vi skriver data til en fil
💡 Læringsmål: I dette kapittelet lærer du hvordan du skriver data til en fil.
Å skrive til fil ligner mye på å lese fra fil, men når vi åpner fila må vi bruke riktig modus, enten w
(write) eller a
(append). Forskjellen på dem er at når fila åpnes med w
vil det eksisterende innholdet i fila slettes, mens a
beholder innholdet, slik at nye ting som skrives legges til på slutten av fila. Og i stedet for å bruke read()
for å lese fila, må vil bruke write()
for å skrive.
with open("adresser.txt", "w", encoding="utf-8") as fil:
fil.write("NRK, Bjørnstjerne Bjørnsons plass 1, 0340 Oslo\n")
fil.write("Slottet, Slottsplassen 1, 0010 Oslo\n")
Koden over åpner fila adresser.txt
i skrivemodus og skriver to linjer til fila. For å få tekst på ny linje må man legge inn linjeskrift selv med \n
. Legg denne koden inn i programmet ditt og kjør programmet. Se at det opprettes en fil som heter adresser.txt
og at denne fila inneholder to linjer, en for hver adresse.
✍️ Oppgave: Flere adresser
- Legg til en eller flere nye linjer med adresser, kjør programmet på nytt og se at adressene blir lagt til i fila.
- Eksperimentèr med å bytte modus fra
w
tila
og kjør programmet. Hva skjer?
✍️ Oppgave: Skrive serietittel til fil
La oss gå tilbake til oppgaven vi gjorde i forrige kapittel da vi leste fra fil; å printe ut alle serietitlene fra serier.txt
. Men i stedet for å skrive ut serietitlene til terminalen skal du nå istedet lagre de til en ny fil, for eksempel i en fil som heter titler.txt
, med èn tittel per linje.
Som vanlig er det flere veier til mål. En mulighet er å først lese fra serier.txt
og istedet for å skrive ut titlene, legge de i en liste. Deretter kan du i en ny with
-blokk åpne fila du vil skrive til, gå gjennom lista, og for hvert element i lista skrive til fil. Et annet alternativ er å åpne begge filene samtidig, både den som skal leses fra og den som skal leses til, og for hver linje man leser fra seriefila skrive direkte til den andre fila med titler.
Det går an å åpne flere filer i samme with
-blokk ved å ha komma mellom open
-kallene. Legg merke til navnene på filvariablene som må være ulike, og man må ha kontroll på hvilken fil man leser fra og hvilken man skal skrive til.
with open("serier.txt", "r", encoding="utf-8") as seriefil, open("titler.txt", "w", encoding="utf-8") as tittelfil:
Feilhåndtering
💡 Læringsmål: I dette kapittelet lærer du hvordan du kan håndtere feil som oppstår når programmet kjører, og dermed unngå at programmet kræsjer uventet.
Prøv og feil
I program kan det av og til oppstå feilsituasjoner som gjør at programmet kræsjer og avslutter. Dette kalles unntak, eller exception
på engelsk. Kanskje har du alt opplevd det selv i dette kurset.
Noen ganger kan det være riktig at programmet avslutter seg selv, mens andre ganger vil vi redde inn situasjonen, slik at programmet kan forsette selv om det skjedde noe feil eller uventet. En forutsetning for at vi skal kunne klare å redde inn feil når de oppstår er at vi har en formening om hva programmet bør gjøre istedet for det programmet forsøkte å gjøre da det feilet. Feil som oppstår når brukere interagerer med programmet er en type feil som bør håndteres på en god måte. Vi skal nå se et eksempel på det.
La oss starte med et lite program som tar inn to tall, a
og b
, som input fra brukeren, og skriver ut resultatet av a/b
i terminalen. Kopier følgende program i en .py
-fil, og kjør programmet.
while True:
a = float(input("Tall a: "))
b = float(input("Tall b: "))
resultat = a/b
print(f"a/b er {resultat}")
Dette programmet er visst en evig dele-maskin. Test ut programmet med ulike input. Hva skjer om du skriver inn bokstaver eller om du angir at b skal være tallet 0?
Forhåpentligvis fikk du programmet til å avslutte uventet fordi det oppsto et unntak. Kanskje ble det skrevet ut noe tekst i terminalen som ligner på dette?
ZeroDivisionError: float division by zero
ValueError: could not convert string to float: 'hallo'
I stedet for at programmet brått avslutter kan vi håndtere feilene og få programmet til fortsette. Det fins en egen type konstruksjon for å håndtere dette, der nøkkelordene try
og except
brukes. Den vanligste og mest grunnleggende bruksmåten er som følger:
try:
# her er koden det kan skje unntak i
except Exception:
# her kan vi gi beskjed til brukeren at det har skjedd en feil og eventuelt gjøre det som trengs for at programmet kan fortsette
Koden som vi vil sikre har vi inni try
-blokka, og håndtering av feil skjer i except
-blokka. Koden man skriver i except
-blokka vil bare bli kjørt om det faktisk skjer et unntak av typen man vil håndtere. I eksempelkoden over er det typen Exception
som angis, det vil si at feil av typen Exception
eller en undertype av Exception
håndteres. Man kan bruke except:
uten å angi en spesifikk feiltype, da fanger man alle slags feiltyper.
La oss starte med det enkleste vi kan gjøre for redde programmet vårt fra å kræsje. Da setter vi all kode som kan kræsje inni en try
-blokk, og skriver ut en feilmelding til brukeren i except
-blokka:
while True:
try:
a = float(input("Tall a: "))
b = float(input("Tall b: "))
resultat = a/b
print(f"a/b er {resultat}")
except Exception:
print("Det skjedde noe feil, prøv igjen!")
Test ut denne endringen i programmet. Hva skjer nå om man deler på 0 eller skriver inn noe som ikke er tall?
Hva som er riktig å gjøre når det oppstår unntak vil avhenge av hva programmet gjør, og hva slags type feil det gjelder. Program som interagerer med brukere bør prøve å gi brukere hjelpsom informasjon om hva som har skjedd, slik at brukeren kan rette opp feilen, og fullføre oppgaven.
Programmet vårt kræsjer ikke lenger, men vi kan gjøre mer for å hjelpe brukeren med å forstå hva som er feil. I blokka except Exception
fanges mange slags typer unntak, og vi kan derfor ikke så lett fortelle brukeren noe mer om akkurat hva som gikk galt når vi bare håndterer den generelle typen Exception
. Men fra det som ble skrevet til terminalen da vi testet ut ulike feilsituasjoner, ser vi at vi har noe som heter ZeroDivisionError
og ValueError
. De ser ut til å gi oss mer informasjon om hva som er feil, så la oss bruke disse feiltypene. Vi kan nemlig håndtere ulike typer feil på ulik måte, ved å ha flere except
-linjer, en for hver type:
except ValueError:
# Håndtere ugyldig input verdi
except ZeroDivisionError:
# håndtere deling på 0
except Exception:
print("Det skjedde noe feil, prøv igjen!")
Det er viktig å merke seg at rekkefølge på except
-linjene har betydning, for håndteringen vil stoppe ved det første uttrykket som matcher på at typen er den samme, eller en undertype, av typen som er angitt i uttrykket. Så hvis except Exception
er først i lista vil alle unntak fanges opp der, fordi de to andre er undertyper av denne.
✍️ Oppgave Erstatt feilhåndteringen i programmet med koden over, og lag passende feilhåndtering for ugyldig verdi og deling på 0.
⚠️ Det kan være på sin plass med en liten advarsel om å fange den generelle feiltypen Exception
eller bare bruke except
uten å angi en spesifikk type. Som vi alt har sett blir ofte tilbakemelding til bruker (eller melding man skriver til en feil-logg e.l.) mer generell, enn om man håndterer mer spesifikke feiltyper. Et annet problem med å fange generelt mange slags feil er at det kan dekke over nye feilsituasjoner som oppstår, slik at man ikke håndterer noe man burde ha håndtert. For eksempel om man henter data fra et API og fanger alle type feil, så vil man kanskje ikke oppdage om responsen fra API-et plutselig har endret seg eller om systemet man henter data fra har problemer. Dette er feil man typisk vil rette opp i så fort som mulig.
Kaste feil
Unntak oppstår fordi de har blitt "kastet" et sted i kode som kjører, ofte fra biblioteker og kode man ikke selv har skrevet. Men man kan også selv kaste unntak med nøkkelordet raise
.
La oss se på et nytt lite program. Kopier følgende linjer i en ny Python-fil, og test ut programmet.
ferdig = False
while not ferdig:
try:
fødselsår = input("Hvilket år er du født? >")
print(f"Du er født i {fødselsår}")
ferdig = True
except Exception:
print("Du har angitt et ugyldig fødselsår, prøv igjen!")
Her er det en try
-except
-blokk, men foreløpig er det ikke noe som gjør at koden i except
-blokka kjøres, alle mulige ting man kan skrive godtas som fødselsår. Men det skal vi forbedre.
Først kan vi starte med å kreve at fødselsår skal være et heltall. Det kan vi fikse ved å forsøke å konvertere fødselsåret til et heltall. Legg inn følgende linje i koden:
fødselsår_tall = int(fødselsår)
Kjør programmet og se at det nå vil be deg prøve på nytt om du skriver noe som ikke er et heltall, hvis du derimot skriver et gyldig tall vil programmet avsluttes. Hva er det som skjer i koden når du blir bedt om å prøve på nytt?
Vi kan gjøre valideringen enda bedre, for ikke alle heltall er noe som kan være et gyldig fødselsår for en person som bruker programmet i dag. Så la oss legge til en litt naiv sjekk på at hvis årstallet er større enn i år eller mindre enn la oss si år 1900 så vil vi også kaste et ValueError
-unntak. Legg til følgende linjer og sjekk hvordan programmet nå oppfører seg med ulik inndata, store og små tall.
if fødselsår_tall < 1900 or fødselsår_tall > 2023:
raise ValueError()
Ser du noen problemer med denne valideringskoden? Er det noe du ville gjort annerledes?
Det går også an å lage sine helt egne unntakstyper istedet for å bruke de som finnes innebygd i Python, slik som ValueError
. For å lage et unntak kan du bruke følgende linje.
class UgyldigÅrError(Exception): pass
Her er det nok litt rar og ukjent syntaks som vi skal lære mer om senere i kurset når vi kommer til objektorientering. Men det koden gjør er å definere en ny type, en klasse som heter UgyldigÅrError
, som arver av klassen Exception
. Det betyr at Python vil gjenkjenne den som et unntak, og man får lov til å kaste den med raise
.
For å kaste unntaket, lager vi en ny instans av klassen med uttrykket UgyldigÅrError()
, som vi så kaster med raise
:
raise UgyldigÅrError()
Endre programmet over til å bruke dette nye unntaket og se at du klarer å fange opp UgyldigÅrError
med except
.
Endelig
Endelig nærmer vi oss sluttet på dette kapittelet, men først skal vi se på hvordan og hvorfor try
-except
kan bygges ut med en finally
.
Noen ganger ønsker vi å forsikre oss om at en kodesnutt blir kjørt, enten koden inni try
-blokka feilet eller ikke. Typisk eksempler er filer eller databasetilkoblinger som man gjerne vil lukke pent etter seg når man er ferdig med å bruke dem.
Se på følgende eksempel. Her åpner vi en fil, vi forsøker å skrive til den, og etter at vi er ferdig med fila ønsker vi å lukke den.
fil = open("adresser.txt", "r", encoding="utf-8")
text = fil.write("Ole Brumm,,Hundremeterskogen\n")
fil.close()
Men om linjene over kjøres, så vil programmet feile, ser du hva som er galt? Kjør programmet og se hva som skjer.
Problemet her er at feilen oppstår i den midterste linja, så linja som sørger for at fila blir lukket vil ikke bli kjørt. Hvordan ville du fikset programmet for å sikre at fil.close()
blir kjørt enten programmet kjørte uten feil eller om det oppstod feil?
En måte er å legge koden inn i en try
-except
, og så lukke fila både i try
-en og i except
-delen, så er man helt sikker på at fila blir lukket. Men det er kjedelig å måtte gjenta seg selv, det kan være lett å glemme å oppdatere det ene stedet, og hva om det skjer en feil som ikke blir fanget i except
-delen? Selv om programmet kræsjer ønsker vi ikke å ødelegge den eksterne fila vi forsøker å lese. Det er her finally
kan redde dagen for oss. Man kan nemlig legge til en tredje kodeblokk med kode man vil skal kjøres avhengig av hva som skjer i try
-blokka.
try:
# her er koden det kan skje unntak i
except Exception:
# her kan vi gi beskjed til brukeren om unntaket og eventuelt gjøre det som trengs for at programmet kan fortsette
finally:
# her er kode som kjøres uavhengig av hva som skjer i try-blokka, det er typisk kode for å rydde opp ressurser som har blitt brukt
Man må ikke ha en except
-blokk for å bruke finally
, i en try
-finally
vil koden i finally
alltid bli kjørt, før programmet eventuelt kræsjer om kode i try
-delen gir unntak, fordi unntaket vil bli kastet videre etter at koden i finally
er kjørt.
✍️ Oppgave: Fiks kodeeksempelet over med en try
-finally
, der finally-delen lukker fila. Skriv gjerne noe ut til terminalen så du kan verifisere at koden i finally
faktisk blir kjørt.
🧠 Visste du at? with
-uttrykket som vi lærte om i kapitlene om å lese/skrive til fil egentlig er en slags avansert try
-with
-finally
, den kjører en finally
som lukker fila for oss. Det betyr at når vi bruker with
så trenger vi ikke å tenke på å lukke fila, det sørger with
for.
Det er verdt å merke seg at finally
kan oppføre seg litt uventet, særlig i kombinasjon med return
, break
og continue
i try
-blokka, koden i finally
vil nemlig kjøres før return
, break
og continue
. En annen ting er at hvis både try
og finally
returnerer en verdi, er det finally
sin returverdi som vinner, og blir returnert. Som vi også har sett vil feil som ikke håndteres av except
kastes videre etter finally
, men hvis finally
har en return
vil ikke det skje.
Flere oppgaver
✍️ Oppgave: Finally
Noen av de litt rare tilfellene med finally
kan du teste ut med følgende kodesnutter. Tenk gjennom hva som foregår i koden, og eksperimenter med å kommentere ut kode eller legge til nye kodelinjer.
def kast_feil():
try:
raise Exception("Det skjedde en feil")
finally:
print("finally")
return True # kommenter ut linja og se hva som skjer
kast_feil()
def hva_returneres():
try:
return False
finally:
return True
print(f"Resultatet er: {hva_returneres()}")
✍️ Oppgave: Feilhåndtering i filbehandling
Ta utgangspunkt i koden for å lese fra fil, og lag et program som tar inn filnavn som input fra brukeren, og skriver ut innholdet i fila i terminalen.
Hva skjer hvis brukeren skriver inn et filnavn som ikke finnes? Legg inn feilhåndtering så brukeren får tilbud om å prøve på nytt.
Klarer du framprovosere feil i koden slik at innholdet i fila du leser går tapt?
Les mer
JSON: Et dataformat
💡 Læringsmål: I dette kapittelet lærer du hva JSON er og hvordan formatet er strukturert, samt hvordan man kan lese og skrive JSON til og fra fil.
JSON står for "JavaScript Object Notation" og er et tekstformat som er relativt leselig for mennesker, og enda enklere for maskiner å lese. Det er basert på JavaScript, men er ellers språkuavhengig.
To strukturer danner oppbyggingen til JSON:
- En samling av par basert på nøkkel og verdi
- En hierarkisk liste med verdier
Disse strukturene er universelle og så å si alle moderne programmeringsspråk støtter disse i en eller annen form. Derfor passer JSON spesielt godt til å overføre data mellom forskjellige tjenester som kan være bygget med forskjellige programmeringsspråk.
Et eksempel på JSON kan for eksempel være:
{
"id": "12975534035527",
"product_code": "DVSF65100022",
"title": "Der ingen skulle tru at nokon kunne bu",
}
Et JSON-objekt kan ha ingen, ett, eller flere "nøkkel-verdi-par", hvor kolon skiller mellom nøkkel og verdi, og komma skiller mellom hvert par. Selve objektet er omgitt av krøllparenteser. Eksemplet over viser ett JSON-objekt med tre nøkkel-verdi-par.
Vi kan utvide eksemplet til inneholde et underobjekt med egne nøkler og verdier:
{
"id": "12975534035527",
"product_code": "DVSF65100022",
"title": "Der ingen skulle tru at nokon kunne bu",
"additional_metadata": {
"category": "3.8.7 Personlig / Livsstil / Familie",
"presentation_format": "2.1.4.99 Dokumentarserie",
"age_restriction": "A",
"production_year": 2022
}
}
I eksemplet over ser vi også at flere datatyper enn strenger støttes for verdier, f.eks. verdien 'production_year' som er et nummer. Datatypene som støttes er strenger, nummer, objekt, lister, boolean, og null.
I eksemplene under vises hvordan disse datatypene kan benyttes i JSON.
Videre kan vi f.eks utvide eksemplet til å inneholde en liste med verdier:
{
"id": "12975534035527",
"product_code": "DVSF65100022",
"title": "Der ingen skulle tru at nokon kunne bu",
"additional_metadata": {
"category": "3.8.7 Personlig / Livsstil / Familie",
"presentation_format": "2.1.4.99 Dokumentarserie",
"age_restriction": "A",
"production_year": 2022
},
"spoken_languages": ["norsk", "sunnmørsk"]
}
Her er et eksempel hvor et JSON-objekt har en liste med underobjekter (episodes
er da en liste med objekter):
{
"id": "12975534035527",
"product_code": "DVSF65100022",
"title": "Der ingen skulle tru at nokon kunne bu",
"additional_metadata": {
"category": "3.8.7 Personlig / Livsstil / Familie",
"presentation_format": "2.1.4.99 Dokumentarserie",
"age_restriction": "A",
"production_year": 2022
},
"spoken_languages": ["norsk", "sunnmørsk"],
"episodes" : [
{
"id": "12975764507527",
"title" : "Øvre Brekkebakkane 3",
"episode_number" : 1,
"ready_for_broadcast": true,
},
{
"id": "12975764519527",
"title" : "Øvre Brekkebakkane 4",
"episode_number" : 2,
"ready_for_broadcast": false,
},
]
}
Og helt til sist, et eksempel på en lengre liste av JSON-objekter. Slik ser det gjerne ut i en JSON-fil:
[
{
"id": "12975534035527",
"product_code": "DVSF65100022",
"title": "Der ingen skulle tru at nokon kunne bu",
},
{
"id": "45975588035556",
"product_code": "MUHU65100022",
"title": "Der alle skulle tru at nokon kunne bu",
},
{
"id": "78455588067599",
"product_code": "NNFA65100022",
"title": "Nyheter om hvor ingen skulle tru at nokon kunne bu",
}
]
✍️ Oppgave JSON-eksemplet under inneholder en rekke feil og mangler. Klarer du å rette opp slik formateringen blir korrekt?
{
"id": "43905584095567",
"product_code" = "MUHU65100022",
"title"; "Nytt på nytt",
"medvirkende": (
"Bård Tufte Johansen",
"Isalill Kolpus,
Johan Golden,
)
"metadata": {
"category" "3.5.7.4 Satire".
"presentation_format": 2.0.8 "Talkshow",
"age_restriction": "9+",
"production_year" - 2023,
}
I Python får man støtte for JSON gjennom standardbiblioteket json
.
Det er fire funksjoner i dette biblioteket som du kommer til å benytte mye når du arbeider med JSON i Python:
load()
, dump()
, loads()
, og dumps()
.
Merk den lille forskjellen mellom de to første funksjonene og de to siste funksjonene, nemlig 's'-endelsen.
Kort forklart så benyttes funksjonene uten 's' når man skal lese eller skrive til en fil, mens funksjonene med 's' benyttes når man skal lese elle skrive til en streng.
Disse funksjonene konverterer henholdsvis JSON fra og til en Python-oppslagstabell.
Det første eksemplet vårt kan for eksempel se slikt ut når man konverterer en JSON-streng til en Python-oppslagstabell:
import json
product_json = '{"id": "12975534035527", "product_code": "DVSF65100022", "title": "Der ingen skulle tru at nokon kunne bu"}'
product = json.loads(product_json)
print(product["title"])
Og omvendt når man konverterer fra en Python oppslags-tabell til en JSON-string:
import json
product = {
"id": "12975534035527",
"product_code": "DVSF65100022",
"title": "Der ingen skulle tru at nokon kunne bu"
}
json_string = json.dumps(product)
print(json_string)
I eksempelet over bruker vi altså funksjonene loads()
og dumps()
fordi vi konverterer JSON fra og til en streng.
I de aller fleste tilfeller der man arbeider med JSON kommer man ikke til å belage seg på at dataen er hardkodet i scriptet sitt. Man ønsker nok heller å hente dataen fra et eksternt sted, f.eks. en fil eller et API.
Vi har tidligere sett på hvordan man kan lese innhold fra en fil, så nå skal vi se videre på hvordan man kan lese JSON fra en fil for deretter å benytte det innebygde json-biblioteket i Python til å konvertere innholdet i fila til en oppslagstabell.
Hvis f.eks. fila inneholder kun ett JSON-objekt
{
"id": "12975534035527",
"product_code": "DVSF65100022",
"title": "Der ingen skulle tru at nokon kunne bu",
}
Så kan man lese hele innholdet av fila som beskrevet i kapittel 2.1, for deretter å benytte det innebygde json-biblioteket i Python for å konvertere slik at det kan legges i en oppslagstabell:
import json
with open("data.json", "r", encoding="utf-8") as fil:
json_data = json.load(fil)
Selv om fila med JSON-data inneholder flere objekter i en liste, f.eks. slik:
[
{ "id": "12975534035527", "product_code": "DVSF65100022", "title": "Der ingen skulle tru at nokon kunne bu"},
{ "id": "13158833767527", "product_code": "NNFA21000023", "title": "Dagsrevyen 21"},
{ "id": "13272583632527", "product_code": "DVFJ20000023", "title": "Norge i dag"}
]
Så kan man lese inn json på samme måte som før, siden en liste av JSON-objekter fortsatt er et gyldig JSON-format.
import json
with open("data.json", "r", encoding="utf-8") as fil:
json_liste = json.load(fil)
Lista kalt json_liste
vil da inneholde en lista av de JSON-objektene du leste inn fra fila, konvertert til oppslagstabeller.
For å kunne skrive JSON til en fil benytter vi oss av nesten samme kode som beskrevet i kapittel 2.2, men istedet for write()
benytter vi json.dump()
:
import json
product = {
"id": "12975534035527",
"product_code": "DVSF65100022",
"title": "Der ingen skulle tru at nokon kunne bu"
}
with open("data.json", "w", encoding="utf-8") as fil:
json.dump(product, fil)
Merk at i eksemplet over så overskriver vi innholdet i fila data.json, men i enkelte tilfeller ønsker vi kanskje å tilføye data til en allerede eksisterende JSON-struktur.
Det kan tenkes at å kun erstatte w
med a
er nok for å kunne legge til innhold i fila, men dette kan tukle med JSON-strukturen som allerede finnes i fila.
Det beste er derfor å lese inn hele innholdet i fila for deretter å legge til ønsket JSON-objekt før man til slutt skriver innholdet til fila på nytt igjen:
import json
with open("data.json", "r", encoding="utf-8") as fil:
json_data = json.load(fil)
new_product = {
"id": "98975534655545",
"product_code": "MUHU65100022",
"title": "Der alle skulle tru at nokon kunne bu"
}
json_data.append(new_product)
with open("data.json", "w", encoding="utf-8") as fil:
json.dump(json_data, fil, indent=2)
⚠️ Obs! I eksemplet over tar vi høyde for at innholdet i data.json er strukturert som en liste av JSON-objekter
Resultatet av koden over er at JSON-fila nå ser slik ut.
[
{
"id": "12975534035527",
"product_code": "DVSF65100022",
"title": "Der ingen skulle tru at nokon kunne bu"
},
{
"id": "98975534655545",
"product_code": "MUHU65100022",
"title": "Der alle skulle tru at nokon kunne bu"
}
]
✍️ Oppgave Prøv deg frem! Ta utgangspunkt i JSON-eksemplene nevnt over, eller lag helt nye strukturer, og gjør et forsøk på å både lese og skrive til fil. Klarer du å f.eks. å lese inn en JSON, gjøre endringer og så skrive den til samme eller ny fil?
Lek og Moro med Elektronisk Program-Guide!
💡 Læringsmål: I dette kapittelet lærer du hvordan enkeltdelene i de foregående kapitlene kan settes sammen og bli til et nyttig program
Vi skal i korte trekk lage et skript som:
- Leser inn Elektronisk Program-Guide (EPG) for flere kanaler fra en JSON-fil.
- Teller opp hvor mange programmer som finnes i hver kategori.
- Skriver ut antall programmer per kategori, i synkende rekkefølge, til terminalen.
Vi har også mange andre ideer til ting du kan eksperimentere med når du er ferdig med listen over, i tillegg får du kanskje ideer selv underveis til ting du vil teste ut.
Hva er Elektronisk Program Guide (EPG)?
EPG er en programoversikt, gjerne for en gitt dato, som viser sendeplanen for lineærkanalene. Oversikten har start- og stopptidspunkt, tittel og annen informasjon om programmene som skal sendes.
Filen(e) vi skal bruke i programmet vårt inneholder json med følgende struktur for en kanal:
{
"id": "epg_nrk1",
"channel": {
"id": "nrk1",
"title": "NRK1",
"sourceMedium": 1,
"isLive": true,
"hasEpg": true,
"isOndemandChannel": true,
"isDistrictChannel": false,
"hasDistrictChannels": true,
"priority": 1
},
"entries": [
{
"programId": "NNFA05022723",
"seriesId": "nyhetsmorgen-tv",
"category": {
"id": "nyheter",
"displayValue": "Nyheter",
"isTvCategory": true,
"isRadioCategory": true
},
"legalAge": null,
"title": "Nyhetsmorgen",
"description": "Nyhetsmorgen, Politisk kvarter og Kulturnytt – alt du trenger for en oppdatert start på dagen. Direkte fra studio 50 hver morgen."
}
]
}
Dette formatet er en forenkling av det som brukes i TV guiden på tv.nrk.no, som passer fint for det vi skal lage nå. Filen inneholder en liste av kanaler, og hver kanal har et felt entries
som igjen inneholder en liste over alle programmene som skal gå på kanalen denne dagen.
I utdraget over vises det én kanal med ett program, men i filen vil det være flere kanaler og hver kanal har flere programmer.
Kom i gang
Start med en tom Python-fil eller ta utgangspunkt i filen epg.py. Denne filen inneholder en funksjon som er kjekk å ha til ekstraoppgavene.
Kopier filen epg.json til samme mappe som Python-filen over. Dette er filen som vi skal få Python-skriptet til å lese og prosessere.
Les json fra fil
Bruk det du lærte i del 1, 3 og 4 til å lese innholdet i epg.json
som json. I første omgang kan du bare skrive ut innholdet av json-objektet til terminalen, som verifikasjon på at filen er lest inn.
Tell opp antall programmer for hver kategori
I stedet for å skrive ut jsonen til terminalen må vi hente ut kategorien for hvert program, og lagre kategoriene i en passende struktur. En mulig måte å gjøre dette på er å lage en oppslagstabell der kategoriene er nøkler og antall programmer for den gitte kategorien er verdien.
{
"nyheter": 7,
"dokumentar": 4
}
For å få til dette må vi iterere over listen av kanaler, og for hver kanal iterere gjennom programmene, og til sist, for hvert program må vi hente ut kategorien. Dette kan for eksempel løses med to for
-løkker inni hverandre. Når man har fått tak i kategorien må oppslagstabellen oppdateres. Om man bruker kategoriens id
som nøkkel må man sjekke om iden finnes finnes i oppslagstabellen fra før, i så fall må man øke verdien med 1. Hvis ikke, må den nye nøkkelen legges til i tabellen.
Gratulerer, du har en fiks ferdig kommandolinjeapplikasjon! 🎉
Lag mer gøy! 🎨
Om du har fullført stegene over kan du utvide programmet ditt hvis du har lyst. Du kan for eksempel prøve på å:
- Skrive antallet programmer per kategori til en JSON-fil, i stedet for til terminalen.
- La programmet spørre brukeren om å angi en kategori, og gi tilbake informasjon om programmer som har denne gitte kategorien. For at brukeren skal kunne velge kategori kan det være lurt å liste ut mulige kategorier i terminalen først.
- La programmet spørre brukeren om en tidsperiode i minutter, og gi tilbake informasjon om programmer som varer like lenge eller kortere. Dette er nyttig om du vil se eller høre på noe men bare har en gitt tid til rådighet.
- Har du en idé selv til en utvidelse, test den ut!
🎯 Kan det du gjør på jobb bli mer effektivt med et Python program?
Temaene vi har jobbet med i dag kan kanskje brukes direkte i noe du gjør på jobb, f.eks. om du manuelt henter ut informasjon fra filer. Om du har filer på andre format enn json
kan de fortsatt leses og tolkes, enten manuelt eller ved å bruke andre biblioteker. Det fins for eksempel en eget csv-bibliotek for å håndtere csv
-filer.
Hente data fra API
💡 Læringsmål: I del 3 av kurset vil du lære å hente data fra API i stedet for å lese data fra fil, du vil også lære om hvordan du kan bruke andres Python-kode gjennom en pakkebehandler, hvordan kommandolinjeargumenter fungerer, og hvordan kode kan organiseres i moduler og pakker.
I denne presentasjonen går vi gjennom konseptene på et overordnet nivå:
Kort oppsummert:
I del 2 lagde vi et program som leste inn ei ferdig fil med NRKs programguide, og hentet ut statistikk basert på den. Men i den virkelige verden er det vanligvis bedre å hente disse dataene fra et sted, automatisk. I løpet av del 3 skal vi derfor lære hvordan vi kan hente data fra internett gjennom et API.
For å kunne hente data gjennom API trenger vi først å lære hvordan vi kan bruke Python-kode andre har skrevet, gjennom en pakkehandler. Det gjør at vi kan bruke en Python-pakke for å håndtere kommunikasjon med API, og dermed slippe å skrive all koden for det selv.
Vi skal også lære å bruke kommandolinjeargumenter som er en mer effektiv måte å bruke programmet der vi kan angi all data programmet trenger med en gang når vi kjører programmet, i stedet for spørre bruker om input underveis i programmet.
Nå som vi skriver stadig mer kode vil det være nyttig å vite hvordan man kan splitte opp kode over flere filer og mapper, som i Python svarer til moduler og pakker. Det skal vi også se på i denne delen av kurset.
Til sist skal vi bruke det vi har lært til å utvide EPG-programmet vårt til å hente data fra et API, og bruke kommandolinjeargumenter til å angi dato og kanaler.
Plan
- Installere/bruke pakker
- Kommandolinje-argumenter
- Kalle API
- Organisere kode
- Mer lek og moro med Elektronisk Program-Guide!
Pakkebehandler og pip
Hvorfor skrive egen kode når du kan bruke andres? Velkommen til jungelen av bibliotekspakker!
Åpen kildekode
Når vi programmerer, ender vi ofte opp med å løse problemer som allerede har blitt løst av andre programmerer mange ganger før. For eksempel det å gjøre nettverkskall til en server, tolke et spesielt filformat, ta i mot nettverkskall fra nettlesere med mer. I stedet for at du må løse dette «ferdig løste» problemet enda en gang, kan du gjenbruke løsninger som andre har skrevet før deg.
Opphavsretten setter begrensninger
I utgangspunktet er all koden som du finner på nettet underlagt opphavsretten. Den begrenser deg sånn at du ikke får lov til å kopiere og gjenbruke andres kode uten tillatelse, på samme måte som du ikke kan skrive avskrift av ei bok du har kjøpt. Du kan dog inngå en avtale med forfatteren som slår fast at du har lov til å bruke koden.
Lisenser: Avtaler som gir deg lov til å bruke kode
Kode som er publisert på nettet for å gjenbrukes, blir som regel gjort tilgjengelig under en «åpen kildekode»-lisens. En lisens er en avtale som du kan velge å følge for å få visse rettigheter under bestemte vilkår, uten at du må kontakte forfatteren, besøke et advokatkontor og signere en avtale i tre eksemplarer. Lisenser for «åpen kildekode» vil som regel gi deg rett til å gjenbruke kildekoden, men bare på visse betingelser. Hvis du bryter med betingelsene så har du ikke lenger rett til å bruke koden.
Ulike lisenser og deres bruksvilkår
De aller fleste lisensene har betingelser som er helt greie å oppfylle. Men du bør være obs på at det finnes copyleft-lisenser som stiller krav til at også din kode må være lisensiert med den samme lisensen. Disse lisensene sørger for at en bedrift ikke bare kan nyttiggjøre seg av koden uten å donere forbedringene tilbake til fellesskapet. Motsatt finnes permissive-lisenser som nesten ikke stiller noen krav i det hele tatt.
Nettstedet https://choosealicense.com/ gir en fin introduksjon til hvilken lisens du bør velge hvis du selv ønsker å dele kildekoden din. På undersiden https://choosealicense.com/appendix/ kan du slå opp lisenser som du finner der ute på nettet, for å få oversikt over hvilke rettigheter de gir deg og hvilke betingelser som følger med.
Åpen kildekode på internett
På nettet kan du finne mye åpen kildekode på nettsteder som GitHub.com.
Du kan kopiere kode fra prosjekter du finner der og bruke den i ditt eget prosjekt,
så lenge koden er lisensiert med en «åpen kildekode»-lisens og du oppfyller lisensens betingelser.
Lisensen ligger som regel i ei fil som heter LICENSE
eller LICENSE.txt
i den øverste mappa i prosjektet,
eller den er nevnt øverst i hver enkelt kodefil.
Hvis du ikke finner noen lisens så må du bare anta at koden ikke er åpen kildekode.
Åpen kildekode er frivillighetsarbeid
Åpen kildekode utvikles i fellesskap. Hvis du finner en feil eller har forslag til forbedring, kan du som oftest melde dette på prosjektets nettside. Du kan også foreslå endringer i koden og bidra som frivillig.
Det er dessverre et problem i dette økosystemet med at vi har veldig viktige åpne programvarer som store bedrifter har gjort seg helt avhengige av å bruke, som sliter med å holde programvaren oppdatert og fri for feil fordi alle er veldig glade i å bruke koden, men ingen er villige til å legge ned tid og krefter i å vedlikeholde den. Som poengert av XKCD:
Pakker
Det å kopiere kode fra andres prosjekter inn i ditt prosjekt har noen svakheter:
- Du får ikke med oppdateringer som blir gjort på koden du kopierte fra, for eksempel hvis det var en viktig sikkerhetsfeil som ble fikset.
- Koden du kopierer fra kan gjenbruke andres kode i tur, som gjør at du potensielt må kopiere inn den koden, og så videre.
Å la deg inspirere av andres åpne kildekode eller kopiere én funksjon eller noen få kodelinjer pleier å gå greit, men det øyeblikket du vil gjenbruke mer så bør du gjøre det i mer ordna former.
For å gjøre det lettere å gjenbruke andres kode, organiserer vi gjenbrukbar kode i pakker. Ei pakke har et pakkenavn og kan også ha flere versjoner, der hver versjon er identifisert med et versjonsnummer. Ei pakke består av ei samling med kodefiler som er pakket sammen i én fil. Disse pakkene kan installeres, litt på samme måte som du installerer et dataprogram på maskinen din. Hver pakke kan også ha avhengigheter (dependencies) til andre pakker, som må installeres for at pakken skal fungere.
For å installere, avinstallere og holde styr på installerte pakker bruker vi en pakkebehandler. I Python er det en innebygd pakkebehandler ved navn pip, men den har ikke noen god innebygd funksjonalitet for å holde styr på hvilke pakker prosjektet ditt krever er installert. Vi skal derfor bruke en pakkebehandler som heter Poetry.
Installere Poetry
Poetry har et eget Python-skript du kan kjøre for å installere det. Følg installasjonsveiledningen til Poetry.
Hvordan legge til Poetry i $PATH
?
Installasjonen nevner at du må ha en viss mappe i $PATH
.
Oppsettet til $PATH
bestemmer hvilke mapper terminalen/skallet skal lete i når du bare skriver navnet på en kommando, for eksempel poetry
.
Hvis ikke mappa som poetry
ligger i er satt opp i $PATH
, vil aldri terminalen finne den kjørbare fila som heter poetry
med mindre du skriver hele filstien dens manuelt hver gang du vil kjøre Poetry.
På Windows kan du legge til denne mappa ved å:
- Trykk på Windows-ikonet
- Søk etter «Rediger miljøvariabler for kontoen din» og kjør den
- Dobbelttrykk på den som heter
Path
i den øverste lista - Trykk «Ny» og skriv inn stien til mappa. Denne stien blir skrevet til terminalen når du installerer Poetry
- Trykk «OK» og «OK»
- Åpne terminalen på nytt for at de nye innstillingene skal tre i kraft
På Unix og Linux er det typisk en fil som blir kjørt av skallet ditt ved oppstart som setter innholdet av $PATH
-variabelen.
Nøyaktig hvilken fil det er, kommer an på hvilket skall du bruker.
For Bash (default i Ubuntu og eldre Mac OS X) er det .bashrc
i hjemmemappa di, for ZSH (default i nyere Mac OS X) er det .zshrc
i hjemmemappa.
Lag fila hvis den ikke finnes, og legg til ei linje på formatet:
export PATH="/sti/til/mappe/med/poetry:$PATH"
Akkurat hvilken sti du skal legge til før kolonet blir skrevet til terminalen når du installerer Poetry.
Dobbeltsjekke at Poetry er installert
Når du har installert Poetry, skal du kunne kjøre poetry --version
i terminalen.
kurs $> poetry --version
Poetry (version 1.3.1)
Din versjon vil sannsynligvis være nyere enn 1.3.1 som er vist ovenfor, men det gjør ingenting.
Sette opp Python-prosjektet ditt
Før du kan begynne å installere pakker, må du sette opp et Python-prosjekt. Et Python-prosjekt er ei mappe med Python-filer som utgjør en logisk enhet. EPG-innleseren fra del 2 er et typisk eksempel på et Python-prosjekt, og hvis du skulle lagd en tale-til-tekst-tjeneste så ville den vært et annet Python-prosjekt.
Alle Python-filene i et Python-prosjekt deler de samme avhengighetene.
For hvert Python-prosjekt hører det til ei pyproject.toml
-fil.
Den inneholder informasjon om prosjektet, hvilken Python-versjon prosjektet krever,
og hvilke pakker som må installeres for at prosjektet kan kjøres.
Bruk Poetry til å lage denne fila for deg med hjelp av poetry init
:
kurs $> poetry init
This command will guide you through creating your pyproject.toml config.
Package name [kurs]: epg-innleser
Version [0.1.0]: 1.0.0
Description []: Analyseverktøy for EPG
Author [Kari Nordmann <kari.nordmann@nrk.no>, n to skip]:
License []:
Compatible Python versions [^3.8]:
Would you like to define your main dependencies interactively? (yes/no) [yes] no
Would you like to define your development dependencies interactively? (yes/no) [yes] no
Generated file
[tool.poetry]
name = "epg-innleser"
version = "1.0.0"
description = "Analyseverktøy for EPG"
authors = ["Kari Nordmann <kari.nordmann@nrk.no>"]
readme = "README.md"
packages = [{include = "epg_innleser"}]
[tool.poetry.dependencies]
python = "^3.8"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Do you confirm generation? (yes/no) [yes]
Fordi vi bare har tenkt å bruke dette Python-prosjektet lokalt så spiller det ikke så stor rolle akkurat hva du putter i de ulike feltene. Navn, versjon, beskrivelse, forfattere og lisens er dog veldig nyttig hvis du skal publisere prosjektet ditt som en pakke andre kan laste ned.
Legge til avhengigheter
Vi bruker kommandoen poetry add <pakkenavn...>
for å legge til en avhengighet.
Når vi gjør det, laster Poetry ned pakken fra en sentral pakkebrønn kalt "The Python Package Index" (PyPI)
som er drevet av "Python Software Foundation", som er de samme som vedlikeholder selve Python-språket.
Før du laster ned ei pakke, bør du søke den opp på https://pypi.org. Der kan du lese hva pakken gjør, hvordan du bruker den og klikke deg inn på nettsida til prosjektet.
Eksempel: La oss legge til cowsay som en avhengighet.
kurs $> poetry add cowsay
Creating virtualenv epg-innleser-ZMuqz6FV-py3.8 in /home/n123456/.cache/pypoetry/virtualenvs
Using version ^5.0 for cowsay
Updating dependencies
Resolving dependencies... (7.4s)
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing cowsay (5.0)
kurs $>
Hvordan kjøre program med Poetry?
Avhengighetene som Poetry installerer for oss blir installert på en sånn måte at de hører til Python-prosjektet vårt. Det betyr at du må gjøre et ekstra steg for å «tre inn i» Python-prosjektet, sånn at du kan få tak i de installerte pakkene.
Hvis du prøver å kjøre cowsay
-pakken som vi installerte i stad, vil vi få en feil:
kurs $> python -m cowsay Dette er kuuuuult
/usr/bin/python: No module named cowsay
Du har to alternativer for å tre inn i Python-prosjektet:
- Kjør én kommando inni Python-prosjektet:
poetry run <kommando>
- Åpne en terminalsesjon inni Python-prosjektet:
poetry shell
. Da kan du kjøre flere kommandoer uten å brukepoetry run
foran. Når du er ferdig kjører duexit
.
Eksempel på å kjøre én kommando med poetry run
:
kurs $> poetry run python -m cowsay -t "Dette er kuuuuult"
_________________
| Dette er kuuuuult |
=================
\
\
^__^
(oo)\_______
(__)\ )\/\
||----w |
|| ||
Eksempel på å kjøre flere kommandoer med poetry shell
:
kurs $> poetry shell
Spawning shell within /home/n123456/.cache/pypoetry/virtualenvs/epg-innleser-ZMuqz6FV-py3.8
. /home/n123456/.cache/pypoetry/virtualenvs/epg-innleser-ZMuqz6FV-py3.8/bin/activate
(epg-innleser-py3.8) kurs $> python -m cowsay -t "Dette var litt møøøøe"
_____________________
| Dette var litt møøøøe |
=====================
\
\
^__^
(oo)\_______
(__)\ )\/\
||----w |
|| ||
(epg-innleser-py3.8) kurs $> exit
exit
kurs $>
❌ Mulige løsninger hvis poetry shell
gir feilmelding på Windows
Jobbmaskiner kan ofte være sperret ned for å beskytte deg fra å kjøre ondsinnet programvare ved et uhell.
Men denne beskyttelsen kan også hindre deg i å kjøre skript som du faktisk har lyst til å kjøre.
Du risikerer derfor at poetry shell
gir deg en feil om at den ikke får lov til å kjøre skript.
Hvordan løse:
- Kjør
exit
for å gå ut av skallet som ble opprettet avpoetry shell
- Kjør
poetry run powershell
i stedet forpoetry shell
- Nå kan du kjøre
python
og andre kjørbare filer som er installert i prosjektet, som for eksempelcowsay
, uten å ta medpoetry run
foran
Hver gang du ville ha kjørt poetry shell
, kjører du poetry run powershell
i stedet.
De to skal fungere likt.
Alternativt kan du velge å bare bruke den første metoden med poetry run
foran hver kommando.
Hvordan bruke en pakke i Python-kode
Som oftest ønsker vi å bruke pakka vi har installert i et Python-program. En pakke kan gi både nye kjørbare program, importerbare moduler, eller begge deler. Vi skal se på hvordan vi kan bruke en importerbar modul fra en pakke.
Siden vi alt har cowsay
-pakka installert, bruker vi denne i eksempelet. Vi starter med å få kua til å si det samme som fra terminalen. Lag en ny Python-fil innenfor samme mappe som prosjektfila pyproject.toml
, og kopier inn linjene under. Den første linja med import
er for at vi skal få tilgang til funksjonene i cowsay
-modulen. Deretter kaller vi cow
-funksjonen med teksten vi vil at kua skal si som parameter.
import cowsay
cowsay.cow("Dette er kuuuuult")
For å kjøre programmet fra terminalen brukes poetry
ganske likt som terminaleksempelet over.
poetry run python <filnavn>.py
Få Poetry til å fungere med VS Code
Hvis vi fortsatt vil kjøre programmer fra VS Code ved å trykke på "play"-knappen, må vi fortelle VS Code hvilken Python-fortolker den nå skal bruke. Det er fordi Poetry lager en egen folder for prosjektet med en Python-fortolker og pakkene vi installerer, så om vi ikke bruker samme fortolker vil VS Code ikke finne pakkene vi har installert.
For å finne stien til Python-fortolkeren til Poetry kan man kjøre følgende kommando i terminalen.
poetry env info
Det vil da listes opp miljøer tilsvarende bildet under. Kopier ut stien som er i executable
under virtualenv
.
For å sette Python-fortolker i VS Code, kan du gå til View
i toppmenyen i VS Code, og deretter velge Command Palette...
. Begynn å skrive python: select interpreter
i kommandofeltet, og klikk på samme tekst når det kommer som valg i nedtrekkslista.
Da har du forhåpentligvis kommet til noe som ligner på bildet under, klikk på Enter interpreter path
, og lim inn stien du kopierte fra terminalen.
Om du har valgt riktig fortolker bør det stå noe om Poetry
i menylinja helt i bunnen av VS Code, ved siden av versjonsnummeret for Python.
Kjør programmet
Kjør programmet og se at kua dukker opp i terminalen!
✍️ Oppgave Det finnes andre innebygde figurer enn ku, for eksempel dragon
, fox
, octopus
, stegosaurus
og turtle
. Disse finnes også som funksjoner, så test å bytte ut kall til cow()
i programmet med en av de andre figurene.
✍️ Ekstraoppgave cowsay
har også funksjonen draw()
, som i tillegg til tekst, lar deg sende inn en streng for figuren man vil skal tegnes under snakkebobla. For eksempel:
fisk = r"""
\
\
/`·.¸
/¸...¸`:·
¸.·´ ¸ `·.¸.·´)
: © ):´; ¸ {
`·.¸ `· ¸.·´\`·¸)
`\\´´\¸.·´
"""
Bruk funksjonen draw
for å få fisken til å snakke. Eller søk etter ascii art på nettet, asciiart.eu er en nettside med mange små kunstverk i ascii. Eller kanskje du vil lage en figur helt selv?
Andre ting du kan gjøre
Vi har sett på disse Poetry-kommandoene allerede:
poetry init
: Sett opp et Python-prosjekt fra scratchpoetry add <pakkenavn>...
: Legg til avhengighetene med pakkenavnene du har oppgittpoetry run <kommando>
: Kjør én kommando inni prosjektetpoetry shell
: Åpne en ny terminalsesjon inni prosjektet (for å kjøre flere kommandoer)
Her er et par andre Poetry-kommandoer du kan bruke:
poetry install
: Installer alle avhengighetene ipoetry.lock
. Nyttig hvis du har kopiert koden til en ny datamaskin og trenger å få på plass alle avhengighetene igjen.poetry remove <pakkenavn>...
: Fjern avhengighetene med pakkenavnene du har oppgittpoetry show <pakkenavn>
: Vis informasjon om den installerte pakka med det angitte pakkenavnetpoetry update
: Oppdater avhengighetenepoetry self update
: Oppdater Poetrypoetry list
: List opp alle Poetry-kommandoene som er tilgjengelige
Videre lesning
Kommandolinje-argumenter
💡 Læringsmål: I dette kapittelet skal du bli kjent med hvordan du kan gi brukeren kontroll over hva applikasjonen skal gjøre, uten at applikasjonen stopper opp underveis.
Når vi skriver kommandolinjeprogram for et visst publikum så må vi tenke på brukeropplevelsen for de som bruker programmet vårt. Nå tenker du sikkert at det gjør da ingenting, for det er bare du som skal bruke programmet du lager. Men du risikerer selv å bli en nybegynner på programmet ditt når det har gått et år siden sist du brukte det, og alle minner om hvordan det fungerte for lengst har forduftet.
Brukergrensesnittet til kommandolinjeprogram kalles the command-line interface (CLI) på engelsk. I dette kapittelet ser vi på ett av mange aspekter ved CLI.
Hvis det er uvant å bruke kommandolinja/terminalen, så kan det hende du har lyst til å lese ekstra-seksjonen om terminalen.
Hvordan kan du la brukeren bestemme ting?
Hvordan kan applikasjonen din vite hvilken fil den skal lese fra? Eller hvilken fil den skal skrive til? Eller hva den i det hele tatt skal gjøre med innholdet i fila?
Vi skal illustrere ulike alternativer vi har for å løse dette problemet.
Eksempelfila ksjefer.txt
Før vi går i gang, kan du lage ei fil som heter ksjefer.txt
med følgende innhold:
Olav Midttun
Kaare Fostervoll
Hans Jacob Ustvedt
Torolf Ester
Bjartmar Gjerde
Einar Førde
John G. Bernander
Hans-Tore Bjerkaas
Thor Gjermund Eriksen
Vibeke Fürst Haugen
Vi skal referere til denne fila når vi kjører eksempelprogrammet vi lager oss.
Problemstillingen
Vi ser på et eksempel der vi skal åpne ei fil og skrive innholdet av fila til terminalen. Hvordan skal programmet vite hvilken fil som skal åpnes?
flowchart user[fa:fa-user] prg["filnavn = ???\nwith open(filnavn) as fil:\n..."] style prg text-align:left user-- python les_fil.py -->prg
Vi tar en kikk på ulike måter vi kan hente filnavnet på.
Hardkode verdien i koden
Den enkleste løsningen er å bestemme alt dette inni applikasjonen, såkalt hardkoding. For eksempel kan du skrive filstien direkte i koden.
flowchart user[fa:fa-user] prg["filnavn = 'ksjefer.txt'\nwith open(filnavn) as fil:\n..."] style prg text-align:left user-- python les_fil_hardkodet.py -->prg
Fullstendig kodeeksempel:
# les_fil_hardkodet.py
filnavn = "ksjefer.txt"
with open(filnavn) as fil:
for linje in fil:
print(linje, end="")
Dette kan fungere helt greit når du tester, men det blir fryktelig upraktisk å måtte endre programmet hver gang du vil lese ei ny fil.
Spørre underveis med input()
I kapittel 1.5 var vi innom input()
-funksjonen,
som lar deg stille brukeren et spørsmål som hen må svare på før programmet fortsetter.
sequenceDiagram actor user as Bruker participant prg as filnavn = input('Hvilken fil? ') <br/>with open(filnavn) as fil:<br/>... autonumber user->>+prg: python les_fil_input.py prg->>+user: Hvilken fil? user->>-prg: ksjefer.txt prg->>-user: Ferdig
Fullstendig kodeeksempel:
# les_fil_input.py
filnavn = input("Hvilken fil? ")
with open(filnavn) as fil:
for linje in fil:
print(linje, end="")
Dette fungerer bra for interaktive applikasjoner der brukeren skal sitte parat ved tastaturet hele veien, men det er ofte mye mer praktisk for brukeren å kunne bestemme alt dette helt i starten, og så gjøre noe annet mens programmet kjører.
Gi verdien samtidig som du starter programmet
I resten av dette kapittelet skal vi se på kommandolinjeargumenter. Dette er verdier som brukeren skriver samtidig som hen starter programmet ditt. For eksempel:
flowchart user[fa:fa-user] prg["import sys\nfilnavn = sys.argv[1]\nwith open(filnavn):\n..."] style prg text-align:left user-- python les_fil_arg.py ksjefer.txt -->prg
Vi kommer tilbake til koden om litt, men kan du se hvor brukeren har skrevet filnavnet hen?
Frem til nå har vi alltid skrevet python
etterfulgt av et mellomrom og navnet på programmet vi ville kjøre.
Men du kan alltids legge til flere argumenter etter navnet på skriptet (her les_fil_arg.py
).
Dette er argumenter til programmet ditt, som det kan lese ut og nyttiggjøre seg av.
I eksemplet ovenfor kjører brukeren python les_fil_arg.py ksjefer.txt
i terminalen.
Programmet python
får to argumenter: les_fil_arg.py
og ksjefer.txt
.
Python tolker og videreformidler disse argumentene sånn at også skriptet vårt kan nyttiggjøre seg av dem.
Funksjons- eller kommandolinjeargument?
Du kjenner kanskje igjen ordet argument fra funksjoner. Hvis vi skulle skrevet dette som en Python-funksjon som tok inn filnavnet som et funksjonsargument, ville det kanskje sett sånn her ut:
# les_fil_func.py
def les_fil(filnavn):
with open(filnavn) as fil:
for linje in fil:
print(linje, end="")
les_fil("ksjefer.txt")
Funksjons- og kommandolinjeargumenter handler begge to om å sende informasjon inn til koden. Forskjellen ligger i om det er en funksjon eller om det er hele programmet som er mottaker.
Lese kommandolinjeargumenter manuelt
La oss starte med den innebygde måten du kan lese kommandolinjeargumenter på.
For å nyttiggjøre deg av de ekstra argumentene brukeren skriver,
kan du importere sys
-modulen.
Deretter kan du lese argumentene fra lista sys.argv
.
Lista i sys.argv
har alltid navnet på skriptet i posisjon 0.
Eventuelle kommandolinjeargumenter ligger i posisjon 1 og utover.
Skrive sys.argv
til terminalen
Er det litt forvirrende hvordan det du skriver i terminalen blir gjort om til sys.argv
i Python?
Vi kan lage oss et program som lar oss se hvordan sys.argv
ser ut, og eksperimentere med det:
# print_argv.py
import sys
print(sys.argv)
✍️ Oppgave:
Lag print_argv.py
lokalt hos deg, og eksperimenter med å kjøre det i terminalen.
Hva printes når du ikke oppgir noe kommandolinjeargument?
Hva printes når du gir mange kommandolinjeargumenter?
Fil-eksemplet med sys.argv
Ovenfor gikk vi gjennom flere måter programmet ditt kan ta inn et filnavn på,
men vi gikk aldri inn på hvordan eksemplet ville se ut med sys.argv
.
Her er det eksemplet:
# les_fil_arg.py
import sys
with open(sys.argv[1]) as fil:
for linje in fil:
print(linje, end="")
Eksempel på kjøring:
kurs $> python les_fil_arg.py ksjefer.txt
Olav Midttun
Kaare Fostervoll
Hans Jacob Ustvedt
Torolf Ester
Bjartmar Gjerde
Einar Førde
John G. Bernander
Hans-Tore Bjerkaas
Thor Gjermund Eriksen
Vibeke Fürst Haugen
✍️ Oppgave:
Hva skjer hvis du bare kjører python les_fil_arg.py
, uten at du oppgir noe navn etterpå?
Lag deg en teori og test det deretter ut. Skjedde det du forventa?
Bruke click
til å tolke kommandolinjeargumenter
Selv om det er greit å vite om sys.argv
, så blir det fort mye arbeid å bruke den direkte.
Vi skal derfor bruke et verktøy som sparer oss for det arbeidet.
Under panseret vil vi fortsatt bruke sys.argv
,
men verktøyet leser fra lista selv og gir oss «gratis» feilhåndtering og mye mer.
Et sånt verktøy er inkludert i Python og heter argparse
,
men i dette kurset skal vi bruke et tredjepartsbibliotek kalt click
.
Start med å installere click:
kurs $> poetry add click
Using version ^8.1.3 for click
Updating dependencies
Resolving dependencies... (0.2s)
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing click (8.1.3)
Versjonsnummeret (8.1.3) vil sannsynligvis være høyere hos deg, men det gjør ikke noe.
Vi kan prøve å konvertere fillesing-skriptet vårt til å bruke click
:
# les_fil_click.py
import click
@click.command()
@click.argument("filnavn")
def les_fil(filnavn):
with open(filnavn) as fil:
for linje in fil:
print(linje, end="")
les_fil()
Dette ser umiddelbart litt rart ut.
Hvorfor sender vi ingen argumenter til les_fil()
-funksjonen?
Svaret er at dekoratørene vi har lagt til – @click.command()
og @click.argument("filnavn")
– gjør om på hvordan funksjonen virker.
Den forventer derfor ikke å få noe argument når du kjører den.
Click vil i stedet lese sys.argv
og sende inn det første program-argumentet til brukeren som funksjons-argumentet filnavn
.
Når du kjører dette i terminalen, oppfører det seg ganske likt med les_fil_arg.py
.
Men det øyeblikket du skriver flere eller færre kommandolinjeargumenter enn programmet forventer,
vil du se at vi har fått en del ny funksjonalitet.
kurs $> poetry run python les_fil_click.py ksjefer.txt
Olav Midttun
Kaare Fostervoll
Hans Jacob Ustvedt
Torolf Ester
Bjartmar Gjerde
Einar Førde
John G. Bernander
Hans-Tore Bjerkaas
Thor Gjermund Eriksen
Vibeke Fürst Haugen
kurs $> poetry run python les_fil_click.py ksjefer.txt hei
Usage: les_fil_click.py [OPTIONS] FILNAVN
Try 'les_fil_click.py --help' for help.
Error: Got unexpected extra argument (hei)
kurs $> poetry run python les_fil_click.py
Usage: les_fil_click.py [OPTIONS] FILNAVN
Try 'les_fil_click.py --help' for help.
Error: Missing argument 'FILNAVN'.
kurs $> poetry run python les_fil_click.py --help
Usage: les_fil_click.py [OPTIONS] FILNAVN
Options:
--help Show this message and exit.
kurs $>
Lage hjelpetekst til Click
Click gir automatisk applikasjonen din støtte for -h
og --help
.
Disse tilvalgene er en etablert konvensjon for kommandolinjeprogram
– du kan jo prøve å kjøre ls --help
eller poetry --help
, for eksempel.
Click leser automatisk doc-strengen som du skriver først i funksjonen. Her bør du skrive en oppsummering på hva skriptet ditt gjør.
Det vanlige er å lage en kort oppsummering på én linje, etterfulgt av ei blank linje og så ei lengre forklaring som godt kan vare flere linjer.
La oss gi eksempelskriptet vårt en egen introduksjon:
# les_fil_click_v2.py
import click
@click.command()
@click.argument("filnavn")
def les_fil(filnavn):
"""
Skriv innholdet av fila med filstien FILNAVN til terminalen.
"""
with open(filnavn) as fil:
for linje in fil:
print(linje, end="")
les_fil()
Nå vil hjelpeteksten være enda mer hjelpsom:
kurs $> poetry run python les_fil_click_v2.py --help
Usage: les_fil_click_v2.py [OPTIONS] FILNAVN
Skriv innholdet av fila med filstien FILNAVN til terminalen.
Options:
--help Show this message and exit.
Posisjonelle argumenter
Den mest grunnleggende formen for kommandolinjeargument er argument som får sin mening ene og alene basert på hvor det står – altså posisjonen.
For eksempel har vi kommandoen cp
(kort for copy) som lager en kopi av ei fil.
Den tar inn to posisjonelle argumenter: Kildefila, og den nye kopien du vil lage:
kurs $> cp les_fil_click_v2.py les_fil_click_v3.py
At les_fil_click_v2.py
er kildefila, er bestemt ene og alene av at den er satt først.
Tilsvarende vet vi at les_fil_click_v3.py
er navnet på kopien,
siden det er det andre posisjonelle argumentet.
Med click
så definerer du nye argumenter ved å bruke @click.argument("argumentnavn")
rett før funksjonsdefinisjonen.
Posisjonen til @click.argument(...)
bestemmer den forventede posisjonen til kommandolinjeargumentet når brukeren kjører skriptet.
Vi kan legge til flere argumenter til utlesingsskriptet vårt, for eksempel for å ta inn en prefiks som skal legges til hver linje:
# les_fil_prefiks.py
import click
@click.command()
@click.argument("filnavn")
@click.argument("prefiks")
def les_fil_med_prefiks(filnavn, prefiks):
"""
Skriv PREFIKS + innholdet av fila med filstien FILNAVN til terminalen.
Prefikset PREFIKS blir skrevet ut på starten av hver linje.
"""
with open(filnavn) as fil:
for linje in fil:
print(prefiks + linje, end="")
les_fil_med_prefiks()
Eksempel på kjøring:
kurs $> poetry run python les_fil_prefiks.py --help
Usage: les_fil_prefiks.py [OPTIONS] FILNAVN PREFIKS
Skriv PREFIKS + innholdet av fila med filstien FILNAVN til terminalen.
Prefikset PREFIKS blir skrevet ut på starten av hver linje.
Options:
--help Show this message and exit.
kurs $> poetry run python les_fil_prefiks.py ksjefer.txt "Kringkastingssjef: "
Kringkastingssjef: Olav Midttun
Kringkastingssjef: Kaare Fostervoll
Kringkastingssjef: Hans Jacob Ustvedt
Kringkastingssjef: Torolf Ester
Kringkastingssjef: Bjartmar Gjerde
Kringkastingssjef: Einar Førde
Kringkastingssjef: John G. Bernander
Kringkastingssjef: Hans-Tore Bjerkaas
Kringkastingssjef: Thor Gjermund Eriksen
Kringkastingssjef: Vibeke Fürst Haugen
Frivillige, posisjonelle argumenter
Alle argumentene du legger til vil være obligatoriske.
Det gjør les_fil_prefiks.py
litt ufleksibel.
Må vi virkelig ha to forskjellige program, ett med prefiks og ett uten?
Hvis vi gjør PREFIKS-argumentet frivillig, kan ett og samme skript brukes enten du vil ha prefiks eller ikke.
For å gjøre et posisjonelt argument frivillig, spesifiserer du hvilken verdi argumentet skal få når brukeren ikke tar det med.
Det gjør du med det navngitte argumentet default=...
i click.argument(...)
.
I eksemplet med prefiksen, så kan vi bare bruke en tom streng som forvalgt verdi.
Da kommer vi til å konkattenere en tom streng med hver linje i fila,
men til gjengjeld slipper vi å lage spesiell kode for når vi ikke har noe prefiks.
Vi behandler rett og slett tilfellet med ingen prefiks som å være likt tilfellet med prefiks lik ""
.
# les_fil_click_v3.py
import click
@click.command()
@click.argument("filnavn")
@click.argument("prefiks", default="")
def les_fil(filnavn, prefiks):
"""
Skriv PREFIKS + innholdet av fila med filstien FILNAVN til terminalen.
Prefikset PREFIKS blir skrevet ut på starten av hver linje, hvis angitt.
"""
with open(filnavn) as fil:
for linje in fil:
print(prefiks + linje, end="")
les_fil()
Eksempel på kjøring:
kurs $> poetry run python les_fil_click_v3.py ksjefer.txt --help
Usage: les_fil_click_v3.py [OPTIONS] FILNAVN [PREFIKS]
Skriv PREFIKS + innholdet av fila med filstien FILNAVN til terminalen.
Prefikset PREFIKS blir skrevet ut på starten av hver linje, hvis angitt.
Options:
--help Show this message and exit.
kurs $> poetry run python les_fil_click_v3.py ksjefer.txt "Kringkastingssjef: "
Kringkastingssjef: Olav Midttun
Kringkastingssjef: Kaare Fostervoll
Kringkastingssjef: Hans Jacob Ustvedt
Kringkastingssjef: Torolf Ester
Kringkastingssjef: Bjartmar Gjerde
Kringkastingssjef: Einar Førde
Kringkastingssjef: John G. Bernander
Kringkastingssjef: Hans-Tore Bjerkaas
Kringkastingssjef: Thor Gjermund Eriksen
Kringkastingssjef: Vibeke Fürst Haugen
kurs $> poetry run python les_fil_click_v3.py ksjefer.txt
Olav Midttun
Kaare Fostervoll
Hans Jacob Ustvedt
Torolf Ester
Bjartmar Gjerde
Einar Førde
John G. Bernander
Hans-Tore Bjerkaas
Thor Gjermund Eriksen
Vibeke Fürst Haugen
Legg merke til oppsummeringslinja i hjelpeteksten:
Usage: les_fil_click_v3.py [OPTIONS] FILNAVN [PREFIKS]
PREFIKS er plassert mellom klammeparanteser ([
og ]
).
Dette er en konvensjon innenfor terminalprogram, og signaliserer at PREFIKS er frivillig.
På samme måte er OPTIONS (som for eksempel --help
) frivillig.
FILNAVN, på den andre siden, er obligatorisk, siden det ikke er omsluttet av klammeparenteser.
Tilvalg som ikke tar inn en verdi (flagg)
Kommandoers virkemåte kan justeres med tilvalg (options). De starter alltid med bindestrek.
Vi har to typer tilvalg:
- Korte tilvalg (short options) er alltid ett tegn lange, og starter med én bindestrek. Eksempel:
-a
,-f
,-l
- Lange tilvalg (long options) kan være flere tegn lange, og starter med to bindestreker. Eksempel:
--all
,--force
,--list
En annen dimensjon ved tilvalg er hvorvidt de tar inn en verdi eller ikke.
Hvis de ikke tar inn verdi, kalles de for flagg.
--help
er et eksempel på et flagg.
I eksemplet ovenfor så vi at click.argument
brukes til å registrere at vi skal ta inn et posisjonelt argument.
Tilsvarende kan vi bruke click.option
til å registrere et tilvalg.
De posisjonelle argumentene til click.option
er tilvalget sånn som du vil at brukeren skal skrive det.
Du kan oppgi flere synonymer, sånn at ett og samme tilvalg har en kort og en lang variant.
Click antar at tilvalg skal ta inn en verdi.
Er det et flagg, må du sette det navngitte argumentet is_flag
til True
.
Da vet også Click at tilvalget skal være enten True
eller False
(altså en boolsk verdi).
For hjelpeteksten så kan du legge ved en beskrivelse i det navngitte argumentet help
.
Det du skriver her vil bli listet opp i Options
-seksjonen når brukeren legger til flagget --help
.
Eksempel: Linjenummer
La oss utvide eksemplet fra i stad med et valg som lar deg skru på nummerering av linjene.
Første linje vil da starte med 1:
, andre linje med 2:
og så videre.
Første spørsmål blir da: Hvordan skal vi få lagt til linjenummeret?
Vi kan utvide for
-løkka sånn at vi ikke bare itererer over linjene i fila, men også får med et løpenummer.
Da kan vi printe løpenummeret til terminalen i forkant av hver linje, men bare hvis brukeren har bedt om det:
# les_fil_click_v4.py
import click
@click.command()
@click.option("--number", "-n", is_flag=True, help="Skriv linjenummer foran hver linje.")
@click.argument("filnavn")
@click.argument("prefiks", default="")
def les_fil(number, filnavn, prefiks):
"""
Skriv PREFIKS + innholdet av fila med filstien FILNAVN til terminalen.
Prefikset PREFIKS blir skrevet ut på starten av hver linje (etter ev.
linjenummer), hvis angitt.
"""
with open(filnavn) as fil:
for linjenummer, linje in enumerate(fil, start=1):
# Skriv linjenummer hvis aktivert
if number:
# Sørg for konsekvent venstremargin
# (for filer på opptil 999 linjer)
print(f"{linjenummer: 3d}: ", end="")
# Skriv linja
print(prefiks + linje, end="")
les_fil()
Med click.option
spesifiserer vi at vi ønsker å legge til et tilvalg.
Kortformen er "-n"
, mens langformen er "--number"
.
Takket være is_flag=True
så vet Click at det er et flagg,
og at det derfor ikke skal ta inn en verdi.
Da vil det også bli tolket som et boolsk tilvalg,
som er False
hvis brukeren ikke tar det med, og True
hvis det er nevnt.
Teksten i help='...'
blir tatt med i hjelpeteksten for dette valget.
Som du ser så har vi dokumentert de posisjonelle argumentene i doc-strengen som er først i funksjonskroppen.
Du kan kun bruke help="..."
med click.option
, ikke click.argument
.
Dette er en designavgjørelse som utviklerne av Click har tatt.
For å få linjenummeret bruker vi funksjonen enumerate
.
Den tar inn noe som du kan iterere over, for eksempel ['a', 'b', 'c']
,
og legger på et løpenummer, for eksempel [(0, 'a'), (1, 'b'), (2, 'c')]
.
Siden elementene er tupler, kan vi pakke dem ut i for
ved å skrive for linjenummer, linje ...
.
For at ikke første linje skal bli kalt linje 0, ber vi enumerate
om å starte på 1 med start=1
.
Når vi skriver f'{linjenummer: 3d}: '
bruker vi formateringsspråket til å be om at linjenummeret formateres med ledende mellomrom, sånn at linjenummeret alltid tar opp tre tegn.
Siden vi forteller print
at den ikke skal skrive noe linjeskift til terminalen etter teksten vår,
vil linja som skrives ut havne på samme linje som linjenummeret vårt.
Eksempel på kjøring:
kurs $> poetry run python les_fil_click_v4.py --help
Usage: les_fil_click_v4.py [OPTIONS] FILNAVN [PREFIKS]
Skriv PREFIKS + innholdet av fila med filstien FILNAVN til terminalen.
Prefikset PREFIKS blir skrevet ut på starten av hver linje (etter ev.
linjenummer), hvis angitt.
Options:
-n, --number Skriv linjenummer foran hver linje.
--help Show this message and exit.
kurs $> poetry run python les_fil_click_v4.py --number ksjefer.txt
1: Olav Midttun
2: Kaare Fostervoll
3: Hans Jacob Ustvedt
4: Torolf Ester
5: Bjartmar Gjerde
6: Einar Førde
7: John G. Bernander
8: Hans-Tore Bjerkaas
9: Thor Gjermund Eriksen
10: Vibeke Fürst Haugen
kurs $> poetry run python les_fil_click_v4.py --number ksjefer.txt "Kringkastingssjef "
1: Kringkastingssjef Olav Midttun
2: Kringkastingssjef Kaare Fostervoll
3: Kringkastingssjef Hans Jacob Ustvedt
4: Kringkastingssjef Torolf Ester
5: Kringkastingssjef Bjartmar Gjerde
6: Kringkastingssjef Einar Førde
7: Kringkastingssjef John G. Bernander
8: Kringkastingssjef Hans-Tore Bjerkaas
9: Kringkastingssjef Thor Gjermund Eriksen
10: Kringkastingssjef Vibeke Fürst Haugen
Tilvalg som tar inn verdi
Ved å utelate is_flag
-argumentet til click.option
får vi et tilvalg som tar inn en verdi.
Det tilhørende funksjonsargumentet vil bli satt til det brukeren skriver i kommandolinjeargumentet etter tilvalget.
Du kan bruke det navngitte argumentet type
til å bestemme hvordan det brukeren har skrevet skal tolkes.
Forventer du for eksempel heltall kan du skrive type=int
.
Tilvalg er valgfrie.
Vanligvis vil verdien være None
hvis brukeren ikke har spesifisert tilvalget,
men du kan sette en egen forvalgt verdi med det navngitte argumentet default
.
La oss gi brukeren mulighet til å filtrere bort linjer som er for korte.
For eksempel vil vi at poetry run python les_fil_click_v5.py --min-length 40 …
bare skriver ut linjene som er 40 tegn eller lengre.
Hvis brukeren ikke bruker --min-length
, gjør vi ikke noen filtrering:
# les_fil_click_v5.py
import click
@click.command()
@click.option("--number", "-n", is_flag=True, help="Skriv linjenummer foran hver linje.")
@click.option("--min-length", metavar="LENGDE", default=0, help="Hopp over linjer med færre tegn enn LENGDE.")
@click.argument("filnavn")
@click.argument("prefiks", default="")
def les_fil(number, min_length, filnavn, prefiks):
"""
Skriv PREFIKS + innholdet av fila med filstien FILNAVN til terminalen.
Prefikset PREFIKS blir skrevet ut på starten av hver linje (etter ev.
linjenummer), hvis angitt.
"""
with open(filnavn) as fil:
for linjenummer, linje in enumerate(fil, start=1):
# Hopp over linjer med for få tegn
antall_tegn_før_linjeskift = len(linje.rstrip())
if antall_tegn_før_linjeskift < min_length:
continue
# Skriv linjenummer hvis aktivert
if number:
# Sørg for konsekvent venstremargin
# (for filer på opptil 999 linjer)
print(f"{linjenummer: 3d}: ", end="")
# Skriv linja
print(prefiks + linje, end="")
les_fil()
Her har vi spesifisert valget --min-length
.
Men funksjonsargumentet vårt heter min_length
.
Dette skyldes at bindestrek ikke er tillat i variabelnavn, for de blir jo tolket som minus.
Click oversetter derfor fra --min-length
til min_length
.
Hvis brukeren ikke bruker valget, blir minstelengden satt til 0 på grunn av default
-argumentet.
I tillegg skjønner Click at brukeren må oppgi et tall,
siden default-verdien vår er et tall.
Alternativt kunne vi spesifisert type=int
som enda et argument til click.option
.
Vi bruker metavar='LENGDE'
til å bestemme hva placeholderen for verdien skal være i hjelpeteksten.
Hadde vi ikke spesifisert metavar hadde den vært INTEGER
siden det er et heltall.
Eksempel på kjøring:
kurs $> poetry run python les_fil_click_v5.py --help
Usage: les_fil_click_v5.py [OPTIONS] FILNAVN [PREFIKS]
Skriv PREFIKS + innholdet av fila med filstien FILNAVN til terminalen.
Prefikset PREFIKS blir skrevet ut på starten av hver linje (etter ev.
linjenummer), hvis angitt.
Options:
-n, --number Skriv linjenummer foran hver linje.
--min-length LENGDE Hopp over linjer med færre tegn enn LENGDE.
--help Show this message and exit.
kurs $> poetry run python les_fil_click_v5.py ksjefer.txt --min-length 19 --number
9: Thor Gjermund Eriksen
10: Vibeke Fürst Haugen
Andre ting du kan gjøre med Click
Når du får et nytt verktøy i hendene frister det kanskje ikke å slå opp bruksanvisningen, men når det gjelder programvarepakker så kan dokumentasjonen være vel verdt et besøk. Ofte kan verktøyet ha løst problemer som du ikke trodde det løste, eller ha muligheter du ikke har tenkt på. Sjekk ut den offisielle dokumentasjone til Click for å oppdage mange flere ting du kan gjøre, som for eksempel:
- Sette et minimum og/eller et maksimum for et valgs verdi, for eksempel kan
--min-length
ovenfor begrenses sånn at negative tall ikke er tillatt - Be Click om å sjekke at et argument eller et valgs verdi er en gyldig filsti som peker mot en eksisterende fil
- Be Click spørre brukeren om verdier på valg som brukeren ikke bruker (likt
input()
) - Lage program som kan gjøre flere ting, avhengig av hvilken underkommando brukeren spesifiserer (på samme måte som
poetry
gjør ulike ting avhengig om du skriverpoetry add
ellerpoetry run
og så videre) - Åpne opp en editor som brukeren kan bruke til å redigere ei fil
- Vise en progressbar
Click kan altså hjelpe til med flere aspekter av CLI enn bare kommandolinjeargumenter.
Be om bekreftelse med Click
I kapittel 1.5 om input()
brukte vi «Vil du fortsette?»-spørsmål som et eksempel på input()
.
Etter mye knoting fikk vi på plass funksjonalitet for å ha en forvalgt verdi og akseptere ulike ja/nei-svar,
men du kan få den samme funksjonaliteten med Click sin funksjon click.confirm()
.
Her er et eksempel på et program som lager tomme filer for deg. Hvis ei fil finnes fra før, vil programmet spørre deg før det overskriver fila:
# lag_tomme_filer.py
import os
import click
@click.command()
@click.argument("filnavn", nargs=-1)
def lag_tomme_filer(filnavn):
for enkeltfil in filnavn:
if os.path.exists(enkeltfil):
prompt = f"{enkeltfil} finnes fra før. Vil du slette innholdet i fila?"
if not click.confirm(prompt, default=True):
print(f"Hopper over {enkeltfil}")
continue
with open(enkeltfil, "w"):
pass
lag_tomme_filer()
Forklaring:
- Med
nargs=-1
iclick.argument()
forteller vi Click at vi tar i mot så mange filnavn som brukeren vil gi oss. Det kan være ingen, én, to, eller mange titalls filnavn. Funksjonsargumentetfilnavn
vil som en konsekvens alltid være ei liste, i dette tilfellet ei liste med strenger. os.path.exists(filnavn)
returnererTrue
hvisfilnavn
finnes,False
hvis ikke.- Når vi kommer til linja
if not click.confirm(prompt, default=True)
vil skriptet vårt settes på pause mens vi venter på svar fra brukeren. Click bruker nemliginput()
under panseret. - Vi bruker
default=True
ellerdefault=False
for å bestemme hvilket svar vi skal anta hvis brukeren trykker[ENTER]
uten å skrive noe. Hvilket valg som er forvalgt kommuniseres til brukeren ved at bokstaven er majuskel. - For å lage en tom fil holder det å åpne den i skrivemodus (
w
). Da blir fila gjort tom. Siden vi ikke skriver noe til fila, blir den bare værende tom.
Eksempel på kjøring:
kurs $> poetry run python lag_tomme_filer.py ksjefer.txt tom_fil.txt
ksjefer.txt finnes fra før. Vil du slette innholdet i fila? [Y/n]: n
Hopper over ksjefer.txt
Hvis du vil spørre brukeren om hen vil fortsette, og avbryte programmet hvis ikke, så har Click lagd en svarvei for deg.
Du kan nemlig legge til abort=True
i argumentlista til click.confirm()
:
# fortsette_click.py
import click
@click.command()
def fortsette():
# Vi gjør oss litt dramatiske, for eksemplets skyld
print("Alle filene vil bli slettet!!")
click.confirm("Vil du fortsette?", default=False, abort=True)
print("Dett var dett. Alle filene er slettet.")
fortsette()
Eksempel på kjøring:
kurs $> poetry run python fortsette_click.py
Alle filene vil bli slettet!!
Vil du fortsette? [y/N]:
Aborted!
kurs $> poetry run python fortsette_click.py
Alle filene vil bli slettet!!
Vil du fortsette? [y/N]: y
Dett var dett. Alle filene er slettet.
Grunnen til at vi ikke brukte abort=True
i det forrige eksemplet, er at du avbryter hele kjøringa og ikke får fortsatt med neste fil.
✍️ Oppgaver
-
Kan du legge til et nytt tilvalg i det siste
les_fil_click_v5
-eksemplet ovenfor? For eksempel et som lar brukeren spesifisere en melding som skal skrives til terminalen helt i starten, før vi begynner å lese fra fila. -
Kan du tilpasse
@click.argument("filnavn")
fra det samme eksemplet sånn at Click sjekker at brukeren oppgir stien til en lesbar, eksisterende fil? Vi har ikke beskrevet hvordan det gjøres, så her må du bryne deg på dokumentasjonen til Click! -
Kan du skrive om den store oppgaven fra del 2 sånn at du tar inn navnet på JSON-fila fra kommandolinjeargumentene i stedet for at den ligger i koden?
Oppsummering
Denne delen av kurset har vært ganske lang. Men når vi skriver kommandolinjeprogram så må vi tenke på brukeropplevelsen for de som bruker programmet vårt – enten det er kollegaer, eller det er deg selv om seks måneder, når du lurer på hva i all verden du holdt på med 😉 En viktig del av brukergrensesnittet til kommandolinjeprogram – the command-line interface (CLI) – er kommandolinjeargumenter.
Med hjelp av kommandolinjeargumenter kan brukeren styre hva programmet ditt skal gjøre,
og hvordan det skal gjøre det.
Programmet kan lese dem fra sys.argv
,
men ved å bruke Click så blir det enklere for oss å støtte de etablerte konvensjonene for kommandolinjeprogram.
Med @click.command()
så gjør vi om funksjonen under til å være et kommandolinjeprogram.
Mellom funksjonen og click.command
-dekoratøren legger vi til flere dekoratører som bestemmer hva programmet skal ta i mot av posisjonelle argumenter og tilvalg.
Nedenfor funksjonsdefinisjonen kan vi kalle funksjonen uten argumenter, siden de leses inn fra sys.argv
av Click.
@click.argument("argumentnavn")
legger til et posisjonelt kommandolinjeargument som vil legges i funksjonsargumentet med samme navn, argumentnavn
.
@click.option("--tilvalg-navn")
legger til et tilvalg som vil legges i funksjonsargumentet tilvalg_navn
.
Click konverterer fra --tilvalg-navn
til tilvalg_navn
automatisk (legg merke til at bindestrek ble til understrek).
Vi kan legge til flere argumenter mellom parentesene for å tilpasse hvordan de fungerer. Vi har sett:
default=...
for å gjøre et posisjonelt kommandolinjeargument frivillig. For tilvalg kan vi bestemme hvilken verdi funksjonen skal få når brukeren ikke har brukt tilvalgetis_flag=True
for å gjøre et tilvalg om til et flagg. Da skal ikke brukeren gi noen verdi til tilvalget, det er nok å bare spesifisere dethelp="..."
for å dokumentere et tilvalg. (Posisjonelle kommandolinjeargumenter må eventuelt dokumenteres i doc-strengen først i funksjonen)type=...
for å konvertere verdien brukeren har skrevet automatiskmetavar="..."
for å bestemme hva placeholderen for verdien skal være i hjelpeteksten
Den første strengen i funksjonskroppen, doc-strengen, blir tatt med i hjelpeteksten du får når du bruker flagget --help
.
Der kan du forklare hva programmet ditt gjør og hvordan det fungerer.
Click har mange flere muligheter, både når det gjelder kommandolinjeargumenter og andre deler av CLI, som for eksempel å gi brukeren tilbakemelding om hvordan det går, og så videre.
Videre lesning
- Click-dokumentasjonen
- Wiki-artikkelen om CLI
- 12 Factor CLI Apps
- Command Line Interface Guidelines
- argparse-dokumentasjonen
Applikasjonsprogrammeringsgrensesnitt (API)
💡 Læringsmål: I dette kapittelet lærer du hva et API er og hva det benyttes til, og hvilke muligheter Python har til å arbeide med et API
Hva er et API?
API står for "Application Programming Interface" og kan defineres som et set med protokoller, definisjoner og verktøy for å bygge og integrere applikasjonsprogramvare. Et API hjelper rett og slett applikasjoner, programvare, og tjenester med å kommunisere og utveksle data med hverandre.
Se for deg at kelneren i en restaurant er et API. På samme måte som kelneren mottar bestillingen din og viderefører den til kjøkkenet på restauranten, så tar et API imot en forespørsel og viderefører denne til programvaren som utfører en bestemt handling. Når programvaren har utført handlingen vil API'et videreføre en respons tilbake til tjenesten som opprinnelige sendte forespørselen, tilsvarende kelneren som kommer med maten når kokken har tilberedt den.
HTTP-meldinger
En type API som vi skal se på og lære mer om i dette kapittelet er web-API.
Web-API som benytter HTTP sender meldinger som følger en bestemt standard, og kan deles inn i to typer; forespørsler og responser (request
og response
). Forespørslene er meldingene som sendes til API'et, mens responsen er svaret som API'et gir til klienten som sendte forespørselen.
Både forespørsler og responser har en lignende oppbygging og struktur:
- En startlinje som beskriver metoden som skal benyttes når det er en forespørsel, eller om meldingen er en respons, en statuskode forteller om forespørselen var vellykket eller førte til feil.
- En valgfri "header" som gir ekstra kontekst eller metadata til meldingen
- En blank linje
- En valgfri "payload body" som utgjør selve innholdet i meldingen.
Eksempel på en HTTP-forespørsel:
POST /bestilling HTTP/1.1
Host: restaurant.com
Content-Type: plain/txt
Forrett: Gresskarsuppe
Hovedrett: Kalkun
Dessert: Iskrem
Drikke: Vann
Den første linjen i eksemplet over viser hva startlinjen inneholder. Det første ordet i linjen er "POST" og dette viser hvilken HTTP-metode som skal benyttes. Videre ser vi "/bestilling" som forteller hva "request-target" er. Altså hvilket endepunkt hos "host" meldingen skal ende opp på. Noen ganger kan "request-target" være en fullstendig URL som f.eks. kan bestå av forskjellige parametere i tillegg, men i vårt eksempel lar vi den være kort og konsis. Det siste vi ser er "HTTP/1.1" som indikerer hvilken versjon av HTTP som benyttes.
De to neste linjene spesifiserer hva som ligger i det som kalles "header". I eksemplet over finnes det to HTTP-headers, host
og content-type
. Den siste delen under linjeskiftet etter "header"-delen er det som kalles "payload body", som utgjør selve innholdet i meldingen som sendes.
Responsen på forespørselen over kan se ut på følgende måte:
HTTP/1.1 201 Created
Content-Type: text/plain
Bestillingen din er registrert! Dine valg er:
Forrett: Gresskarsuppe
Hovedrett: Kalkun
Dessert: Iskrem
Drikke: Vann
Takk for din bestilling! Den blir snart behandlet.
I responsen er inneholder første linje en statuskode i stedet for HTTP-metode og url som det var i forespørselen.
Metoder
Web-API som benytter HTTP operer med et sett med metoder for forespørslene ("request methods") som sendes. Disse metodene benyttes for å spesifisere hva slags type handling som skal utføres. Det finnes en rekke av disse metodene, men i dette kapittelet skal vi forholde oss til kun noen få, og da spesifikt de som dekker CRUD akronymet (Create Read Update Delete). Ikke alle API'er forholder seg helt likt til denne betegnelsen, men i dette kapittelet forsøker vi å forholde oss til den vanligste bruken av metodene som samsvarer med CRUD.
HTTP Verb | CRUD ekvivalent | Beskrivelse |
---|---|---|
POST | Create | Denne metoden sender data til API som mottar forespørselen, og betegner som oftest at noe skal opprettes i systemet bak tjenesten. |
GET | Read | Denne metoden benyttes for å spørre etter data hos et API. Selve forespørselen inneholder ikke noe body, men responsen fra API'et vil inneholdet dataen som ble etterspurt. |
PUT | Update | Denne metoden benyttes for å oppdatere noe i tjenesten bak et API. |
DELETE | Delete | Denne metoden benyttes for å slette noe i tjenesten bak et API. |
Statuskoder
Statuskoden i en respons forteller hvordan det gikk med behandlingen av en forespørsel. Den kan ha feilet, da sier statuskoden noe om hva slags type feil som skjedde. Hvis forespørselen var vellykket kan statuskoden gi nyttig tilleggsinformasjon.
Statuskoden er en tresifret kode der første siffer sier noe om hva slags type status det dreier seg om, tabellen viser de ulike typene.
Statuskode | Type | Forklaring |
---|---|---|
1xx | Informerende | Forespørselen er mottatt og den prosesseres |
2xx | Vellykket | Forespørselen ble vellykket mottatt, forstått og akseptert |
3xx | Videresending | Forespørselen ble videresendt |
4xx | Klientfeil | Forespørselen inneholder feil og kan ikke fullføres |
5xx | Tjenerfeil | Tjeneren feilet i å fullføre forespørselen |
De mest vanlige statuskodene er de som starter på 2, 4 og 5. Det er vanlig å se 200 OK
når alt gikk bra, 201 Created
om noe har blitt opprettet på tjenersiden, og 204 No Content
om forespørselen var vellykket, men responsen er tom. Av feilkoder er de vanlige 400 Bad Request
om klienten sender noe feil, 401 Unauthorized
eller 403 Forbidden
om man ikke har tilgang til tjenesten eller ikke har autentisert seg på riktig måte. På tjenersiden er 500 Internal Server Error
og 503 Service Unavailable
vanlige, den første er typisk at noe uventet skjer på tjeneren, mens den siste forekommer når en tjener er overbelastet.
Header
Header-delen av en melding består av linjer som er delt inn i nøkkel og verdi (som i Pythons oppslagstabeller), med en kolon som skilletegn. Det er kun ett nøkkel-verdi-par per linje.
Dataene som fins header-felter er som oftest ikke interessante for en sluttbruker, men brukes for å utveksle informasjon mellom tjener og klient. Det fins en rekke faste nøkler som brukes, i tillegg kan tjener og klient fritt legge til egne.
Av de vanlige er Accept
som klienten kan bruke for å angi hva slags type innhold den ønsker å få tilbake i body, og både klient og tjener kan angi Content-Type
for å si hva slags type innhold som er i body i meldingen de sender. For eksempel application/json
for meldinger som inneholder JSON eller text/html
for meldinger som inneholder html. I sistnevnte tilfelle vil headeren i meldingen inneholde Content-Type: text/html
. Wikipedia har en liste over vanlige mediatyper.
Body
En HTTP forespørsel eller respons inneholde informasjon i det som kalles "payload body", eller bare "body", dette er den egentlige informasjonen som partene i meldingsutvekslingen ønsker å sende til hverandre. Tar vi utgangspunkt i eksemplet over med kelneren i restauranten kan "body" i forespørselen sammenlignes med selve bestillingen som kelneren mottar fra deg. Responsen tilsvarer det kelneren kunne svart tilbake. Han gjentar bestillingen så du kan sjekke at den ble riktig, og han forteller at den snart lages.
Innholdet i "body" kan være ren tekst, eller det kan være JSON, XML, HTML eller andre formater, informasjon om hva meldingen inneholder finnes i header-feltet Content-Type
. Når en forespørsel sendes kan headeren Accept
settes for å angi hva slags format klienten godtar for innholdet i responsen.
Arbeide med API'er i Python
I et web-API tar forespørslene form som en URL, og ja, du kan for enkelte forespørsler ofte lime inn URL'en i nettleseren din og få svaret servert tilbake. Hvis du for eksempel benytter følgende URL og skriver denne inn i adressefeltet til nettleseren vil du se at du får en tilbakemelding i JSON-formatet. https://psapi.nrk.no/ipcheck. Her gjør altså nettleseren din en GET-forespørsel mot ps-api og svaret den mottar vises i nettleservinduet, litt som med en vanlig nettside.
I Python har man en rekke bibliotek og rammeverk for å sende spørringer og motta svar fra API. Et populært og mye brukt bibliotek er requests. Det er dette biblioteket vi kommer til å benytte i dette kapittelet, men du står selv fritt til å velge andre HTTP-bibliotek eller rammeverk som finnes i Python hvis du ønsker.
✍️ Oppgave Biblioteket requests
er ikke standard i Python, og må derfor installeres. Installer requests
basert på det du lærte om pakkebehandling i kapittel 3.1.
Etter at requests
er installert kan vi prøve å sende samme forespørslen mot ps-api som over, men denne gangen fra et Python script og ikke via nettleser.
import requests
respons = requests.get('https://psapi.nrk.no/ipcheck')
print(respons.status_code)
print(respons.headers)
print(respons.text)
Variabelen response
er et objekt av typen Response som inneholder mye ulik informasjon om responsen. Blant annet .status_code
som er nyttig for å vite om kallet gikk bra eller ikke, .headers
for å hente ut headerne og selve responsen kan man hente ut som rå tekst .text
.
I eksempelet over ser vi at responsen egentlig er på JSON-format, og da ønsker man vanligvis i kode å få dette parset til en en oppslagstabell, slik som da vi tidligere leste JSON fra fil. Bibloteket requests
har heldigvis støtte for dette også, responsobjektet har metoden json()
som man kan kalle for å få ut.
✍️ Oppgave Lag en variabel for å holde på resultatet av å kalle response.json()
, og skriv ut IP-en din, som fins i feltet clientIpAddress
i JSON-objektet
✍️ Oppgave Test å legge til noen tilfeldige tegn i slutten av urlen som brukes i requests.get()
, hva slags respons får du da?
✍️ Oppgave Det fins et eget api som gir farger til radio og tv, nemlig fargerik. Og det har et eget endepunkt for å hente ut fargene som brukes for en gitt tv-kanal. For eksempel https://fargerik-psapi.nrk.no/tv/channel/nrk1
for å hente ut fargene for NRK 1. Bruk requests.get()
til å kalle endepunktet. Skriv ut headere, og finn ut hva slags Content-Type
som brukes i responsen.
Vi vil gjerne ha JSON tilbake, og API-et støtter at hvis vi ber om JSON ved å bruke Accept
-headeren når vi sender responsen, får vi JSON tilbake. Requests sin get
-funksjon kan ta inn et argument, headers
, som må være en oppslagstabell der nøkkel er navn på headeren man vil sette og verdien er verdien headeren skal ha. I vårt tilfelle vil vi sette Accept
til å være application/json
. Gjør kallet igjen men nå med parameteren headers
, og se at responsen du får tilbake er på JSON-format.
✍️ Ekstraoppgave Hittil har vil bare brukt GET
-verbet, men det kan være fint å bli kjent med de andre verbene også. For å få til det, bruker vi tjenesten reqres.in, som har et gratis API laget nettopp for testing. Her kan vi teste både GET
, POST
, PUT
og DELETE
med et API av brukere. Men API-et returnerer tilfeldige data. Du kan tilsynelatende lage, endre og slette brukere, og få en suksess-statuskode tilbake, uten at du faktisk endrer noe i systemet.
Test først å hente ut en list av brukere med en get-forespørsel til https://reqres.in/api/users
, og se at du får en liste av brukere tilbake.
Deretter kan du teste ut å lage en ny bruker ved bruk av post-metoden, som krever at man sender med innhold i meldingen.Vanligvis er innholdet som kreves definert av API-et, men i dette test-API-et kan man sende inn hva man vil.
Definer en bruker med de feltene du ønsker, i form av en oppslagstabell. For eksempel:
bruker = {
"fornavn": "Testus",
"etternavn": "Testesen"
}
Prøv deretter å "opprette" brukeren ved å kalle sende en POST
-melding til API-et. Biblioteket vi bruker har støtte for alle verbene, så i stedet for å bruke requests.get
må vi bruke requests.post
. I tillegg må vi få sendt inn brukeren som body i meldinga. Det fins en egen parameter json
vi kan bruke for å sende med data i meldingen som skal være på JSON-format. (For annen type data kan parameteren data
brukes). Når json
-parameteren brukes vil også Content-Type
bli satt til application/json
.
response = requests.post("https://reqres.in/api/users", json = bruker)
Sjekk statuskode og innhold i responsen, er det som forventet?
Å oppdatere med PUT
eller slette med DELETE
er handlinger knyttet til et bestemt objekt, så vi må spesifisere hvilket objekt vi vil modifisere. Det løses vanligvis ved at urlene vi kaller har med id-en til objektet vi vil oppdatere eller slette. Det gjelder også for test-API-et, urlen må ha med id til slutt, https://reqres.in/api/users/{id}
.
I test-API-et gjør vi ikke faktiske endringer så vi kan bruke en tilfeldig id, for eksempel tallet 1
. Prøv å gjøre oppdatering og sletting med følgende linjer:
respons = requests.put("https://reqres.in/api/users/1", json = bruker)
response = requests.delete("https://reqres.in/api/users/1")
Sjekke statuskode og innhold for hver av responsene. Hvorfor har kallet til put
med bruker som json-data, mens delete
ikke har det?
Videre lesning
- HTTP - Wikipedia
- HTTP Headers - Wikipedia
- Mediatyper - Wikipedia
- HTTP Statuskoder - Wikipedia
- Dokumentasjon av Requests-biblioteket
Organisering med moduler og pakker
💡 Læringsmål: I dette kapittelet lærer du hvordan du kan organisere koden på en strukturert måte så det blir lettere å finne fram
Moduler
Etterhvert som programmet vokser, og man skriver stadig mer kode, kan det fort oppleves rotete og uoversiktlig om all kode er i samme fil. For å få programmet mer ryddig kan man derfor strukturere koden i flere filer, der funksjonalitet som hører sammen eller handler om det samme er i samme modul.
En modul i Python er ikke noe annet enn en fil med python-kode, altså slike filer vi allerede kjenner til, som slutter på .py
.
La oss si at vi vil lage et program som tar som input en tekststreng som brukeren oppgir, og skriver denne teksten, gjort om til å være på røverspråk, tilbake i terminalen.
Hittil har vi skrevet all kode i samme fil, men nå skal vi lage en røverspråk-modul.
Start med å lage en fil som heter røverspråk.py
. Denne filen vil ha funksjonen til_røverspråk
, som tar inn en tekststreng, og returnerer denne teksten omgjort til røverspråk. Lim inn koden under i røverspråk.py
.
konsonanter = "bcdfghjklmnpqrstvwxz"
def til_røverspråk(tekst):
str = ""
for bokstav in tekst:
if bokstav in konsonanter:
str += f'{bokstav}o{bokstav}'
else:
str += bokstav
return str
Nå vil vi lage selve programfilen som spør brukeren om en tekst, konverterer denne til røverspråk, og skriver den ut i terminalen. Lag en fil som for eksempel heter program.py
, og
lim inn koden under.
import røverspråk
tekst = input("Hva vil du si på røverspråk?> ")
print(røverspråk.til_røverspråk(tekst))
I koden er det et par ting vi kan merke oss. Den første linja, import røverspråk
, trenger vi for å få tilgang til funksjonen til_røverspråk
som er i modulen røverspråk.py
. Da kan vil på den fjerde linja kalle funksjonen ved å skrive røverspråk.til_røverspråk
. Dette er god måte å importere modulen på, for når vi ser modulnavnet røverspråk
skjønner med en gang hvor funksjonen til_røverspråk
kommer fra.
Kjør programmet og se at det fungerer som du forventer!
Andre måter å importere på
Det finnes også andre måter i importere moduler på i filen der man vil bruke modulen.
import røverspråk as rs
medrs.til_røverspråk
der man kaller funksjonen. Dette er nyttig hvis navnet på modulen er litt langt,rs
er kortere å skrive ennrøverspråk
.- Man kan unngå helt å ha modulnavn som prefiks ved å importere med
from røverspråk import til_røverspråk
. Da bruker man bare funksjonsnavnettil_røverspråk
der funksjonen kalles. Det blir mindre å skrive, men det kan være vanskeligere å vite hvor funksjonen kommer fra om man har mange importeringer. - En siste variant er å importere med
from røverspråk import *
. Da har man importert alle navnene frarøverspråk
inn i program-modulen. Det som er skummelt med denne varianten er at man lett kan miste kontroll over hva som er importert, og man kan ved uhell overskrive navn som allerede er tatt i bruk. Det er derfor anbefalt å unngå denne måten å importere på.
Kjøre modul som et program
Noen ganger er det praktisk å også kunne kjøre en modul som et program, samtidig som den fungerer som en modul for at annet program. Vi skal gjøre et lite eksperiment. Lim inn linjene under nederst i fila røverspråk.py
.
røverspråk = til_røverspråk("nrk er verdens beste arbeidsplass")
print(røverspråk)
Kjør nå fila røverspråk.py
som et skript, på samme måte som du kjørte program.py
i stad. Da blir du antakelig ikke så overrasket over at terminalen skriver ut:
nonrorkok eror voverordodenonsos bobesostote arorbobeidodsospoplolasossos
Men gå tilbake til program.py
og kjør programmet på nytt. Hva skjer nå?
Der vil det nå skrives ut følgende til terminalen:
nonrorkok eror voverordodenonsos bobesostote arorbobeidodsospoplolasossos
Hva vil du si på røverspråk?>
Så nå har endringen vi gjorde i røverspråk.py
ødelagt funksjonaliteten i programmet vårt. Det er fordi Python kjører koden i modulen røverspråk.py
når den importeres! Det er derfor viktig å tenke på at man ikke har kode på rotnivå i filen som gjør ting, det som kaller sideeffekt, nettopp som funksjonen print
som skriver ut.
Men det er heldigvis mulig å få til begge deler, både å unngå å ødelegge for programmet som bruker modulen, og samtidig kunne kjøre modulen som et program som gjør noe. Måten man løser dette på å bruke det at moduler har navn.
I python finnes det en global variabel __name__
som inneholder navnet på modulen. Modulen røverspråk.py
vil ha navnet røverspråk
, men når røverspråk.py
kjøres som program vil den få navnet __main__
. Derfor kan vi løse problemet vårt ved å legge til en if-test i røverspråk.py
. Vi vil bare skrive ut nrk er verdens beste arbeidsplass
når navnet er __main__
. Gjør derfor om røverspråk
til å inneholde en if-test:
if __name__ == "__main__":
røverspråk = til_røverspråk("nrk er verdens beste arbeidsplass")
print(røverspråk)
Test å kjøre både program.py
og røverspråk.py
, og se at begge programmene nå fungerer som de skal.
Pakker
Når man har flere moduler som inneholder lignende funksjonalitet eller er relatert til hverandre kan det noen ganger være fint å samle de i en pakke, slik at de importeres med <pakkenavn>.<modulnavn>
. For eksempel om vi lager en ny modul med språket Leet, kunne det være fint å samle røverspråk og leet i en pakke, la oss kalle den språk.
En pakke i Python er en filmappe som inneholder en fil som heter __init__.py
. Det er den litt mystiske Python-filen som forteller Python at denne mappen er en modul. Så i mappen der du har filene dine kan du nå lage mappen språk
, med filen __init__.py
, og så flytter du røverspråk.py
inn hit. Da har filene dine følgende struktur:
språk/
__init__.py
røverspråk.py
program.py
<eventuelt andre filer>
Om man nå skal importere røverspråk
-modulen i program.py
må man prefikse med språk
, så det blir språk.røverspråk
.
✍️ Oppgave:
Fiks programmet program.py
slik at det fungerer nå som røverspråk er i en pakke
Nå vil vi gjøre om tekst til leet også. Lag en fil som heter leet.py
i mappa språk
, og kopier inn følgende kode:
erstatninger = {
'o': '0',
'i': '1',
'z': '2',
'e': '3',
'a': '4',
's': '5',
'g': '6',
't': '7',
'B': '8',
'p': '9'
}
def til_leet(tekst):
str = ""
for bokstav in tekst:
str += erstatninger.get(bokstav, bokstav)
return str
✍️ Oppgave:
Oppdater program.py
så den også bruker funksjonen til_leet
. Du velger selv hvordan du ta i bruk funksjonen, om brukeren må velge om teksten skal oversettes til røverspråk eller leet, eller om du bare skriver ut begge oversettelsene.
Ekstraoppgaver
Her er et par ekstraoppgaver hvis du har litt ekstra tid og vil leke mer med språk.
✍️ Oppgave:
Oversettelsen til røverspråk har den svakheten at den bare støtter små bokstaver. Skriv om til_røverspråk
slik at den også håndterer store bokstaver, og erstatter for eksempel B
med BOB
✍️ Oppgave:
Les mer på om Leet på den engelske wikipedia-siden, og legg til noen nye erstatninger av bokstaver, for eksempel K = |<
, D = |)
.
Les mer
Mer lek og moro med Elektronisk Program-Guide!
💡 Læringsmål: I dette kapittelet vil du lære hvordan enkeltdelene fra de foregående kapitlene kan brukes sammen for å utvide og forbedre EPG-programmet
Nå som vi både har lært om kommandolinjeargumenter, kan hente data fra API , og kan lage moduler og pakker, kan vi forbedre programmet fra kapittel 2.5.
Tidligere brukte vi en fil for å arbeide med EPG-data på JSON-format, men disse dataene finnes også i et API, som vi nå vil bruke istedet.
Det spesifikke endepunktet for EPG-data er https://psapi.nrk.no/epg/
, og og endepunktet har egen dokumentasjon. Det kan være litt uvant å lese en slik dokumentasjon i starten, men på høyre side kan vi se urlen til endepunktet, det står at det er en GET-metode med sti /epg/{channelIds}
, og vi ser hvordan responsen kan se ut.
I midtdelen står det noe om en path-parameter channelIds
og en query-parameter date
.
Parameteren channelIds
er påkrevd, og den må være en kommaseparert liste av kanal id-er man vil hente EPG for, for eksempel nrk1
for bare NRK1, nrk2,p1,p2
for NRK2, P1 og P2. Siden den er en path-parameter skal den være en del av stien, og settes inn i urlen der det står {channelIds}
. Parameteren date
er en query-parameter på formatet yyyy-mm-dd
, og om den utelates, får man EPG for dagens dato.
Eksempler på bruk av endepunktet
Url | Forklaring |
---|---|
https://psapi.nrk.no/epg/p1 | EPG for kanalen P1 på dagens dato |
https://psapi.nrk.no/epg/nrk2?date=2023-10-01 | EPG for kanalen NRK2 på datoen 1. oktober 2023 |
https://psapi.nrk.no/epg/nrk2,p1,p2 | EPG for kanalene NRK2, P1 og P2 på dagens dato |
https://psapi.nrk.no/epg/p1,p13,nrksuper,nrk1?date=2023-04 | EPG for kanalene P1, P13, NRK Super og NRK1 på datoen 1. januar 2023 |
Hent EPG fra API-et
Oppdater EPG-programmet ditt fra tidligere til å hente data fra API-et i stedet for å lese fra fil. Bruk API-et uten å bruke parameteren for dato, og med de kanalene du ønsker.
Formatet på JSON-en som kommer fra API-et matcher med fila vi brukte tidligere, forskjellen er at JSON-en fra API-et inneholder flere felter. Det betyr at koden vi skrev tidligere for å hente ut informasjon fra JSON-strukturen i fila fortsatt bør fungere når vi går over til å bruke API-et i stedet. Men virkelighetens data er ikke like perfekt som dataene i fila, det kan for eksempel hende at et program ikke har kategori. Det kan være at det vil kræsje programmet ditt, så det må du i så fall rette opp i. En mulig løsning er å bruke en jukse-kategori ukjent
om programmet ikke har kategori.
La bruker angi dato og kanaler
Bruk det du har lært om kommandolinjeargumenter til å la bruker angi datoen og kanalene for EPG-en programmet skal bruke for å lage kategori-statistikk.
For å sette datoen som query-parameter når man gjør kall til API-et, kan man bruke en format-streng som vi har brukt når vi vil bruke verdi fra variabel i en tekst. Men det som er ryddigere, særlig om man har flere en én query-parameter, er bruke bibliotekets egne funksjonalitet for disse parametrene. Da må man lage en oppslagstabell for parametrene, sende de med som parameter params
til requests.get
.
parametre = {
"date": "2023-10-16"
}
respons = requests.get("https://psapi.nrk.no/epg/nrk2", params = parametre)
Kanalene, som er en path-parameter må fortsatt settes med en format-streng.
Eksperimenter med å angi ulike kanaler og datoer, og se om du ser noen mønstre i hvilke kategorier som brukes.
Rydd i koden
Er det noe i programmet som kan ryddes? Lag moduler og eventuelt pakker der du synes det passer. Det kan være at lange sekvenser med kode bør deles opp i flere mindre funksjoner som kun utfører én oppgave, og som har et beskrivende navn. Kanskje er det også noen testutskrifter som kan fjernes, navn på variable og funksjoner som kan bli bedre, og lignende.
Grafisk brukergrensesnitt og objektorientering
💡 Læringsmål: I del 4 av kurset vil du bli kjent med objektorientert programmering i Python, og du vil lære å lage grafiske brukergrensesnitt med PySimpleGUI.
Hittil i kurset har vi laget applikasjoner som man bruker via kommandolinjen. Vi kan på en måte si at dette er programmer som "snakker" og "forstår" tekst.
Man kan gjøre mye nyttig med tekst og kommandolinjeapplikasjoner, men noen ganger er enklere å se hvordan ting henger sammen hvis man har en visualisering. Ved å lage applikasjoner med grafiske brukergrensesnitt, kan vi lage programmer som "snakker" til oss gjennom skjermbilder, og som "forstår" hva vi mener når vi bruker knapper, tekstfelter, menyer, og mye mer.
For å lage grafiske brukergrensesnitt, bruker man ofte rammeverk, som inneholder komponenter som knapper, tekstfelter, vinduer og menyer. Disse komponentene kan vi bruke som byggeklosser for å lage våre egne skjermbilder. I denne delen av kurset, blir vi kjent med PySimpleGUI, som er et enkelt, men fleksibelt, rammeverk for å lage grafiske brukergrensesnitt i Python.
Rammeverk for utvikling av grafiske brukergrensesnitt, er ofte utviklet med objektorientert programmering, og PySimpleGUI er intet unntak. Det betyr at vi må kjenne litt til hva objekter og klasser er, og hvordan disse tingene fungerer i Python, for å bruke PySimpleGUI. Vi starter derfor del 4 av kurset med å se litt på objektorientert programmering i Python, og vi blir kjent med dataklasser, som er en enkel måte å komme i gang med objektorientering i Python.
Hva betyr GUI?: Forkortelsen GUI (fra engelsk: Graphical User Interface) betyr det samme som grafiske brukergrensesnitt, og brukes gjerne fordi det er mye kortere å skrive og uttale.
Plan
- Objektorientert programmering i Python
- Dataklasser
- Grafiske brukergrensesnitt med PySimpleGUI
- Mer om PySimpleGUI
Objektorientert programmering i Python
💡 Læringsmål: I dette kapittelet skal du lære om objektorientert programmering og hvordan du kan skrive dine egne klasser for å binde sammen egenskaper og oppførsel i objekter
Python er et objektorientert språk (OOP), som betyr at et program er strukturert slik at egenskaper og oppførsel er representert i objekter. Et objekt kan for eksempel representere et menneske med egenskaper som navn, alder eller by og oppførsel som for eksempel å gå, snakke eller puste. Eller det kan representere en handlekurv med egenskaper som beskriver hvilke varer som er i handlekurven og oppførsel for å legge til eller fjerne varer. Også typer som du har lært om tidligere, som for eksempel strenger, tall og lister, er representert som objekter i Python.
Sagt på en annen måte så kan man si at objektorientert programmering er en måte å modellere virkeligheten på. Både ved å definere egenskapene og oppførselen til forskjellige ting, men også relasjonen mellom dem.
En annen måte å gjøre det på kan være prosedyreorientert programmering der man strukturer et program som en slags oppskrift med forskjellige steg. Stegene kan være bygd opp av funksjoner og kode som utføres etter hverandre for å utføre en oppgave. Hovedforskjellen er at objektene er i sentrum av objektorientert programmering. Ikke bare for å representere data, som i prosedyreorientert programmering, men også i selve struktureringen av kode.
De fleste moderne programmeringsspråk, for eksempel Java, C# og C++, følger prinsipper fra OOP, så mye av teorien lenger ned vil man kunne dra nytte av hvis man hopper videre til andre programmeringsspråk på et senere tidspunkt.
Tips: Når du går gjennom dette kapittelet, kan det være lurt å lage en kodefil som heter dataklasser.py
i mappen kurs/
, og bruke denne til å teste ut de forskjellige kodesnuttene du støter på.
Definere klasser
En klasse er en blåkopi eller en mal for å lage objekter. Den definerer egenskapene og oppførselen til objektene som skal opprettes. Egenskaper kalles ofte attributter eller variabler, mens oppførselen er definert av funksjoner som kalles metoder.
For eksempel kan vi definere en klasse som heter "Menneske", som inneholder attributter som "navn", "alder", og "by", og metoder som "gå" og "snakke". Slik gjøres det i Python:
class Menneske:
art = "Homo sapiens"
def __init__(self, navn, alder, by):
self.navn = navn
self.alder = alder
self.by = by
def gå(self):
print(self.navn, "går")
def snakk(self):
print(self.navn, "snakker")
Her er det forskjell på attributter som er definert i __init__
, såkalte objektattributter, og attributter som er definert i selve klassen, klasseatributter. Objektattributter er egenskaper som hører til hvert nye objekt, mens klasseattributtene er noe som er felles for alle nye objekter av en klasse.
✍️ Oppgave 1 Kan du skrive kode i datastrukturer.py
som lager en klasse for en tv-kanal (kalt Kanal
) som inneholder et felt for kanalnavn (kalt navn
) og en liste over tv-programmer (kalt programmer
) som vises på kanalen? Lag også en funksjon for å liste ut hvilke programmer som vises på kanalen i valgfritt format.
Instansiere objekter fra klasser
Vi kan så opprette objekter av klassen "Menneske" for å representere konkrete personer i koden vår. Dette kalles for å instansiere et objekt. For å gjøre dette kaller man konstruktøren __init__
med de nødvendige parameterne:
m1 = Menneske("Vibeke", 54, "Oslo")
m2 = Menneske("Gry", 52, "London")
Her har vi instansiert et objekt av klassen Menneske som heter "m1" med navnet "Vibeke", alderen 54 og byen "Oslo" og et annet objekt. Nå kan man hente ut attributtene til objektet ved å bruke punktnotasjon:
print(m1.navn) # output: Vibeke
print(m1.alder) # output: 54
print(m1.art) # output: Homo sapiens
print(m2.navn) # output: Gry
print(m2.alder) # output: 52
print(m2.art) # output: Homo sapiens
Her kan man bite seg merke i at attributen art er den samme på begge objektene selv om de ikke er sendt inn i instansieringen av objektene.
Man kan også kalle metodene til objektet for å utføre handlingene de representerer:
m1.gå() # output: Vibeke går
m1.snakk() # output: Vibeke snakker
✍️ Oppgave 2 Lag to instanser av klassen Kanal
med kanalnavn "nrksuper" og "nrk2" med programmer henholdsvis ["Supernytt", "Minibarna", "Fantus og maskinene"]
og ["Filmavisen","Med hjartet på rette staden"]
.
Bruk av type()
Det er mulig å bruke den innebygde funksjonen type()
for å se hvilken klasse et objekt tilhører. Sjekk for eksempel disse objektene:
print(type(m1)) # output: <class '__main__.Menneske'>
print(type(a1)) # output: <class '__main__.Ansatt'>
a = 5.2 # <class 'float'>
b = 'Hello World' # <class 'str'>
c = [1, 2, 3] # <class 'list'>
d = False # <class 'bool'>
e = range(4) # <class 'range'>
f = (1, 2, 3) # <class 'tuple'>
g = complex(1, -1) # <class 'complex'>
for var in [a, b, c, d, e, f, g]:
print(type(var))
✍️ Oppgave 3 Sjekk at kanalene du har instansiert er av klassen Kanal
ved å skrive ut typen til kanalene
Arv
Arv er en måte å lage nye klasser som baserer seg på andre klasser. Den nye klassen kalles en avledet klasse, mens den eksistertende klassen kalles en baseklasse. Slik kan man lage en klasse som baserer seg på klassen vi definerte i tidligere.
class Ansatt(Menneske):
pass
Her kan man se at et objekt av den avledede klassen kan bruke egenskaper og oppførsel som er definert i baseklassen.
a1 = Ansatt("Vibeke", 54, "Oslo")
a2 = Ansatt("Gry", 52, "London")
print(a1.navn) # output: Vibeke
a1.gå() # output: Vibeke går
print(a2.navn) # output: Gry
a2.snakk() # output: Gry snakker
Man kan også bruke isinstance()
for å se om et objektet er utledet av en annen klasse.
print(type(m1)) # output: <class '__main__.Menneske'>
print(type(a1)) # output: <class '__main__.Ansatt'>
print(isinstance(m1, Menneske)) # output: True
print(isinstance(m1, Ansatt)) # output: False
print(isinstance(a1, Menneske)) # output: True
print(isinstance(a1, Ansatt)) # output: True
Her kan man se at en ansatt er et menneske, men et menneske ikke er en ansatt 🤯
Man kan også legge på egne egenskaper og oppførsel spesifikt for den avledede klassen. For å sette egne egenskaper for klassen, må man lage en egen __init__
-funksjon som vi gjorde tidligere. Når man gjør det overskriver man __init__
-metoden fra baseklassen, så derfor må man huske å sette inn verdiene som hører til baseklassen inn i __init__
-metoden til baseklassen. Det gjør man ved å bruke den innebygde super()
-funksjonen.
class Ansatt(Menneske):
def __init__(self, navn, alder, by, arbeidsgiver, stilling):
super().__init__(navn, alder, by)
self.arbeidsgiver = arbeidsgiver
self.stilling = stilling
def ansattpresentasjon(self):
print(f"{self.navn} er {self.alder} år og jobber som {self.stilling} hos {self.arbeidsgiver}.")
a3 = Ansatt("Fredrik", 46, "Oslo", "NRK", "Programleder")
a3.ansattpresentasjon() # output: Fredrik er 46 år og jobber som Programleder hos NRK.
I ansattpresentasjon
-funksjonen bruker man egenskaper som både er definert for Menneske og Ansatt.
Innkapsling
Innkapsling er en programmeringsteknikk i objektorientert programmering som har som formål å hindre direkte tilgang til tilstanden til et objekt fra objekter av andre klasser. Dette vil man gjøre for å være sikker på at tilstanden til objektet er gyldig og at man har kontroll på hvilke endringer som blir gjort.
Uten at feltene er innkapslet, så er det for eksempel veldig lett å endre på instansen m1
:
m1.går() # output: Vibeke går
m1.navn = 123
m1.går() # output: 123 går
For Menneske-klassen vil innkapsling se slik ut:
class Menneske:
def __init__(self, navn, alder):
self.__navn = navn
self.__alder = alder
def get_navn(self):
return self.__navn
def set_navn(self, navn):
if isinstance(navn, str) and len(navn) > 0:
self.__navn = navn
def get_alder(self):
return self.__alder
def set_alder(self, alder):
if isinstance(alder, int) and 0 <= alder <= 150:
self.__alder = alder
Ved å legge til to understreker foran attributtene __navn og __alder, gjør vi dem private. I Python har man fortsatt tilgang på private attrributter, men ved å legge til med dobbel understrek, så tydeliggjør man at de ikke burde brukes utenfor klassen. Tilgangen til attributtene gis gjennom getter- og settermetoder, som også gir oss mulighet til å legge til validering. Ved å gjøre dette lager vi et brukergrensesnitt ut til brukere av klassen som vil gjøre det lettere å gjøre endringer internt i klassen på et senere tidspunkt.
Slik vil bruken av Menneske-objektet se ut:
m3 = Menneske("Harald", 86)
m3.set_navn(123)
print(m3.get_navn()) # output: Harald
✍️ Oppgave 4 Implementer innkapsling for Kanal
-klassen. Gjør navnet til kanalen og listen over programmer privat og tilby en metode for å legge til et program i listen.
Internmetoder
Vi har allerede lært om internmetoden __init__
som brukes for å lage instanser av en klasse. Vi har også flere internmetoder som for eksempel __eq__
som brukes når man sammenligner objekter. Hvis vi for eksempel sammenligner to instanser som er laget av en klasse som ikke implementerer en egen __eq__
-funksjon vil man få False
på om instansene er like siden de refererer til to forskjellige objekter, men i noen tilfeller vil man definere hva som gjør to instanser av en klasse like.
class Rektangel:
def __init__(self, høyde, bredde):
self.høyde = høyde
self.bredde = bredde
def __eq__(self, other):
return (self.høyde == other.høyde) and (self.bredde == other.bredde)
r1 = Rektangel(1,2)
r2 = Rektangel(1,2)
print(r1 == r2) # output: True
En Rektangel
-klasse uten __eq__
ville gitt False
.
En annen internmetode som det kan være greit å ha kjennskap til er __str__
. Den brukes når man for eksempel bruker print() og str(). Hvis du ikke definerer denne metoden for en klasse, vil Python bruke en standard representasjon, som ofte ikke er særlig brukervennlig. Her flytter vi ansattrepresentasjon-koden inn i __str__
i stedet for å ha det i en egen metode.
class Ansatt(Menneske):
...
def __str__(self):
return f"{self.navn} er {self.alder} år og jobber som {self.stilling} hos {self.arbeidsgiver}."
print(m1) # output: <__main__.Menneske object at 0x107e4bd30>
print(a1) # output: Fredrik er 46 år og jobber som Programleder hos NRK.
✍️ Oppgave 5 Legg til __eq__
- og __str__
-funksjoner for klassen Kanal
Dataklasser
Data Classes, også kalt dataklasser er en relativt ny, innebygd funksjonalitet i Python som gjør det lettere og sikrere å definere klasser. Data class transformerer en vanlig klasse til en klasse med mange av de objektorienterte funksjonene fra forrige kapittel ut av boksen. For eksempel init, repr og eq. Det gjør at man får mye av den samme funksjonaliteten med færre kodelinjer. En annen viktig del av dataklasser er bruk av typer i klassedeklareringen slik at det er enklere å jobbe med dataene i klassen når man vet hva den forventer.
Hvordan lager jeg en data class?
For å bruke denne innebygde funksjonaliteten så må man bruke en dekoratør. Kort fortalt er dekoratører et verktøy som lar oss utvide oppførselen til en funksjon eller klasse uten å endre selve funskjonen eller klassen permanent. Les mer om dekoratører her.
Slik kan man lage klassen fra forrige kapittel ved å bruke dataklasser.
from dataclasses import dataclass
@dataclass
class Person:
navn: str
alder: int
by: str
p1 = Person("Vibeke", 54, "Oslo")
print(p1) # Output: Person(Navn="Vibeke", Alder=54, By="Oslo")
p2 = Person("Vibeke", 54, "Oslo")
print(p1==p2) # Output: True
Her ser vi at vi har definert hvilken type et felt skal ha og hvis man for eksempel prøver å lage en Person med "54" som alder, så vil dette hintet dukke opp:
Expected type 'int', got 'str' instead
✍️ Oppgave 1 Skriv om Kanal-klassen fra forrige kapittel til å bruke dataclass. Hint: Kanskje den innebygde str-funksjonen for dataclass også viser informasjon om programmene i hver kanal?
Hvilke andre funksjoner til dataclass kan det være lurt å vite om?
Bruk av standard-verdier
I tillegg til vanlige type-annoteringer, kan man også spesifisere default-verdier.
@dataclass
class Bil:
merke: str
modell: str = "Ukjent"
Her vil modell ha en default verdi lik "Ukjent" dersom den ikke spesifiseres.
Uforanderlige dataklasser
Ved å sette parameteren frozen=True kan du lage en uforanderlig dataklasse:
@dataclass(frozen=True)
class UforanderligPunkt:
x: float
y: float
Etter at et objekt av denne klassen er opprettet, kan du ikke endre attributtene. Ethvert forsøk på å gjøre det vil resultere i en FrozenInstanceError.
Bruk post-init for validering
Dersom du ønsker å utføre validering eller andre handlinger etter at et objekt er initialisert, kan du definere en post_init metode:
@dataclass
class Produkt:
navn: str
pris: float
def __post_init__(self):
if self.pris < 0:
raise ValueError("Pris kan ikke være negativ!")
I eksemplet ovenfor vil en ValueError bli kastet dersom du prøver å lage et Produkt med en negativ pris.
✍️ Oppgave 2 Legg til en validering for Kanal-klassen som sjekker at navnet til kanalen ikke er en tom streng og at kanallista ikke er tom
Når skal jeg bruke dataclass og når burde jeg bruke en vanlig klasse?
Data classes er veldig nyttig når det ikke er noe kompleks logikk i konstruktøren til klassen. Noen ganger skjer det for eksempel validering og databasetilkoblinger i konstruktøren og da kan det være lurt å ha en helt egen konstruktør for det. Når det er sagt, så kan det være ryddigst å bruke klasser for å kun holde på data og da er Data classes veien å gå.
Grafiske brukergrensesnitt med PySimpleGUI
💡 Læringsmål: I dette kapittelet lærer du litt om hva grafiske brukergrensesnitt er, og du får laget ditt første grafiske brukergrensesnitt med PySimpleGUI.
Hva er et grafisk brukergrensesnitt?
Programmer som har et grafisk brukergrensesnitt, er programmer som lar oss interagere med det på en visuell måte. Det er programmer som viser oss ett eller flere vinduer, hvor vi kan klikke på knapper, fylle inn tekst i bokser, og se informasjon og resultater tegnet som mer eller mindre hjelpsomme visuelle elementer. Kort fortalt: "vanlige" programmer.
Du har allerede laget programmer med kommandolinje-baserte brukergrensesnitt i dette kurset. Sånne typer programmer var vanligere før, og brukes fremdeles mye av folk som jobber med utvikling, eller andre datatekniske ting. Det er enklere å utvikle, men kan være litt vanskelige å bruke, og noen ganger kan man få bedre innsikt i dataene man jobber med, hvis de fremstilles på en visuell måte.
I dag er det programmer med grafiske brukergrensesnitt som er de vanligste. De er faktisk så vanlige, at vi typisk sløyfer ordet grafisk, og bare sier at programmet har et brukergrensesnitt, når vi snakker om programmer med grafiske brukergrensesnitt. Den engelske forkortelsen GUI (Graphical User Interface) er også mye brukt.
Et enkelt brukergrensesnitt med PySimpleGUI
Når man lager brukergrensesnitt, er det sjelden man starter helt fra bunnen. I dette kurset har vi valgt å ta utgangspunkt i rammeverket PySimpleGUI, som er utviklet for å gjøre det lett å komme i gang med å lage enkle brukergrensesnitt i Python.
Installasjon av PySimpleGUI
PySimpleGUI er en pakke, og kan installeres med Poetry, som andre pakker. Start med å lage en egen mappe under kurs/
-mappen, for eksempel kurs/gui/
. Her lager du et nytt Python-prosjekt med poetry init
.
$> cd kurs/gui/
kurs/gui $> poetry init
This command will guide you through creating your pyproject.toml config.
Package name [gui]: hei-gui
Version [0.1.0]: 1.0.0
Description []: Mitt føreste brukergrensesnitt
Author [Teodor <teodor@test.no>, n to skip]:
License []:
Compatible Python versions [^3.8]:
Would you like to define your main dependencies interactively? (yes/no) [yes] no
Would you like to define your development dependencies interactively? (yes/no) [yes] no
Generated file
[tool.poetry]
name = "hei-gui"
version = "1.0.0"
description = "Mitt føreste brukergrensesnitt"
authors = ["Teodor <teodor@test.no>"]
readme = "README.md"
packages = [{include = "hei_gui"}]
[tool.poetry.dependencies]
python = "^3.8"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Do you confirm generation? (yes/no) [yes]
Når du har laget det nye Python-prosjektet, kan du installere PySimpleGUI med poetry add pysimplegui
kurs/gui $> poetry add pysimplegui
Creating virtualenv hei-gui-EIg_wOJT-py3.8 in /.cache/pypoetry/virtualenvs
Using version ^4.60.5 for pysimplegui
Updating dependencies
Resolving dependencies... (0.3s)
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
• Installing pysimplegui (4.60.5)
Hei GUI!
Med PySimpleGUI installert, er vi klar til å lage et veldig enkelt brukergrensesnitt. Lag filen kurs/gui/program.py
, og legg inn koden under.
import PySimpleGUI as sg
layout = [
[sg.Text('Hei GUI!')]
]
window = sg.Window('Hei GUI', layout)
running = True
while running:
event, values = window.read()
if event == sg.WIN_CLOSED:
running = False
window.close()
Når dette er på plass, kan du kjøre programmet med poetry run python program.py
. Hvis alt gikk bra, skal det dukke opp et lite vindu som vist under.
Hjelp! Programmet feiler med ModuleNotFoundError: No module named 'tkinter'
PySimpleGUI er avhengig av tkinter. Ofte er tkinter allerede tilgjengelig, men på noen maskiner må man installere dette selv.
Jeg bruker Ubuntu: På Ubuntu, eller andre Linux-distribusjoner som bruker APT, kan du installere tkinter med:
$> sudo apt install python3-tk
Jeg bruker Mac: På Mac kan tkinter installeres med Homebrew:
$> brew install python-tk
Jeg bruker Windows: På Windows skal tkinter være installert sammen med Python, så hvis du får denne feilen her, er det verdt å sjekke at du har installert Python 3, og ikke den veldig gamle versjonen Python 2.
Jeg har prøvd triksene over, men det fungerer fortsatt ikke: Ta kontakt med en veileder, så kan dere sammen gå over denne guiden som viser i mer detalj hvordan man installerer tkinter.
Hvordan fungerer Hei GUI?
Topp! Vi har et vindu med tekst, men hvordan fungerer egentlig koden vi akkurat kjørte?
Det første vi gjorde var å importerer pakken PySimpleGUI, og gi den navnet sg i programmet vårt. Det betyr at når vi senere i programmet skriver sg
, så er det kode i PySimpleGUI vi bruker.
import PySimpleGUI as sg
Det neste vi gjorde var å lage en layout
-variabel, som beskriver hvordan brukergrensesnittet skal se ut. PySimpleGUI fungerer sånn at man beskriver brukergrensesnitt som en liste med rader i skjermbildet, hvor hver rad inneholder en liste av PySimpleGUI-komponenter som skal vises på samme rad.
I koden vår trengte vi bare en rad med en sg.Text
-komponent som inneholdt teksten "Hei GUI!", så derfor ble det hele ganske enkelt.
layout = [
[sg.Text('Hei GUI!')]
]
Skulle vi heller laget et brukergrensesnitt med en tekst, etterfulgt av to knapper på samme rad, kunne det heller sett ut litt som under.
layout = [
[sg.Text('En tekst')],
[sg.Button('En knapp'), sg.Button('En annen knapp')]
]
Kjører vi programmet med denne layout
-koden, ender vi opp med skjermbildet vist under.
Etter vi har fått på plass en layout
, lager vi vinduet som brukergrensesnittet vises i. I tillegg gir vi vinduet en tittel, som er teksten som vises øverst i rammen på vinduet.
window = sg.Window('Hei GUI', layout)
Hvis vi ikke gjorde noe mer, ville programmet vårt startet, åpnet vinduet, og med en gang lukket det. Vi trenger derfor å passe på at programmet ikke stopper før vi lukker vinduet. En måte å gjøre det på, er å lage en løkke som kjører så lenge brukeren vil kjøre. Inni løkken kan vi sjekke om brukeren har forsøkt å lukke vinduet, og hvis det er tilfelle, stopper vi løkken.
For å få til dette, har vi i programmet under laget en boolsk variabel running
, som vi setter til True
når programmet starter. Så bruker vi while running:
for å få en løkke som kjører hele tiden. For å avslutte programmet, kan vi da sette running
til False
, når brukeren forsøker å lukke vinduet. Da vil løkken stoppe, og programmet avsluttes.
running = True
while running:
event, values = window.read()
if event == sg.WIN_CLOSED:
running = False
Å kjøre en uendelig løkke, hvor vi gjentatte ganger sjekker om brukeren har gjort noe, er en vanlig teknikk å bruke når man lager programmer som skal vente på input fra en bruker. I dataspill har til og med denne løkken et eget navn: game loop.
Til slutt må vi passe på å lukke vinduet når brukeren har bedt om å lukke vinduet, og vi har brutt ut av den uendelige løkken.
window.close()
Mer om PySimpleGUI
💡 Læringsmål: I dette avsnittet lærer du mer om hvordan PySimpleGUI fungerer, ved å utvide brukergrensesnittet du laget i forrige seksjon med mer funksjonalitet.
Vi lager en knapp som lukker vinduet
Knapper er en av de vanligste komponentene man bruker når man lager brukergrensesnitt. Man kan bruke knapper for å gi brukeren mulighet til å kjøre forskjellige deler av programmet, som å starte et søk, lagre en fil, eller åpne et bilde.
Den første knappen vi skal lage, skal bare hjelpe oss med å lukke vinduet i brukergrensesnittet vi laget i forrige seksjon.
For å legge til en knapp i brukergrensesnittet, må vi legge den til i layout-listen i starten av programmet, som vist under.
layout = [
[sg.Text('Hei GUI!')],
[sg.Button('Lukk')]
]
Her bruker vi sg.Button('Lukk')
for å lage en ny knapp nederst i grensesnittet, med teksten "Lukk".
Hvis du kjører programmet nå skal du få opp et skjermbildet med en knapp du kan trykke på. Når du trykker på knappen skjer det ingenting. Det er fordi vi ikke har skrevet noe kode som gjør noe når man trykker på knappen.
For å lukke vinduet når man trykker på knappen, kan vi se hva verdien av event
-variabelen er. Når man trykker på knappen, vil denne variabelen få samme verdi som teksten på knappen. Det betyr at vi kan skrive litt kode som setter running
-variabelen til false, og dermed avslutter programmet.
running = True
while running:
event, values = window.read()
if event == sg.WIN_CLOSED:
running = False
elif event == 'Lukk':
running = False
Prøv å kjøre programmet igjen når du har lagt til denne koden. Klarer du å lukke vinduet med knappen nå?
Fargetemaer
Man kan endre hvordan komponentene i PySimpleGUI ser ut ved å endre fargetemaet. Du kan prøve dette ut ved å sette fargetemaet til "DarkTeal6" i starten av programmet.
import PySimpleGUI as sg
sg.theme('DarkTeal6')
Kjører du programmet nå, skal det se litt annerledes ut enn sist.
Hvis du vil ha oversikt over alle de innebygde fargetemaene i PySimpleGUI, kan du bruke en innebygde forhåndsvisningen. Legg til kodelinjen under i starten av programmet, og se hva som skjer når du starter det.
sg.theme_previewer()
I tillegg til å bruke innebygde fargetema, kan du også endre på et eksisterende tema, eller lage ditt eget tema.
Printe tekst fra GUI
Når man skal debugge brukergrensesnitt, er de ikke alltid like lett å printe ut tekst. Dette har PySimpleGUI en egen funksjon for! Under er tre eksempler på hvordan man skriver ut debug-tekst i PySimpleGUI. Legg de inn i programmet ditt og se hvs som skjer.
sg.Print('Helt vanlig tekst')
sg.Print('Grønn tekst', text_color='green')
sg.Print('Hvit tekst på rød bakgrunn', text_color='white', background_color='red')
✍️ Oppgave: _Kan du bruke sg.Print
til å skrive ut debug-tekst som viser når programmet starter, og når en bruker trykker på Lukk-knappen?_
Input-felter
På samme måte som det er nyttig å kunne lese inn kommandolinjeargumenter i kommandolineapplikasjoner, kan det være nyttig å lese inn input fra brukeren i applikasjoner med grafiske brukergrensesnitt.
En vanlig måte å få inn input fra brukeren, er ved å bruke et input-felt hvor brukeren kan skrive inn tekst, og i eksempelet under, skal vi oppdatere programmet vårt sånn at det kan lese inn navnet til brukeren, og si hei hvis brukeren trykker på en knapp.
Vi kan starte med å oppdatere layout
-listen vår som vist under.
layout = [
[sg.Text('Hva er navnet ditt?'), sg.InputText(key='input-navn')],
[sg.Text(key='text-svar')],
[sg.Button(key='knapp-hei', button_text='Si hei til meg!')]
]
Her bruker vi key
-parameteren til å gi noen av komponentene en nøkkel, som gjør at vi kan slå de opp senere. Dette gjør det lettere å f.eks. lese ut navnet brukeren har skrevet inn.
Hvis vi kjører programmet nå, ser vi at vi har fått et brukergrensesnitt med tekst, et input-felt og en knapp, men igjen er det ingenting som skjer når vi trykker på knappen.
For å få programmet til å si hei til brukeren må vi:
- Sjekke om brukeren har trykket på knappen, ved å se om
event
-variabelen er satt til'knapp-hei'
. - Hvis dette er tilfelle, må vi først lese ut navnet fra input-feltet. Dette kan vi gjøre direkte fra
values
-variabelen. - Vi må så hente tekstfeltet vi skal skrive svaret til fra
window
-variabelen. - Til slutt må vi oppdatere tekstfeltet med svaret, ved å bruke
.update(...)
funksjonen til feltet.
Kodeutsnittet under viser hvordan vi kan få til disse fire tingene. Denne koden må skrives inne i den uendelige løkken som kjører mens vinduet er åpent.
elif event == 'knapp-hei':
name = values['input-navn']
greetingText = window['text-svar']
greetingText.update(value=f'Hei {name}!')
Hvor finner jeg mer informasjon om PySimpleGUI?
I denne seksjonen har vi sett litt på noen få komponenter i PySimpleGUI, men når du skal lage dine egne brukergrensesnitt, kan det fort være at du trenger mange flere. Det betyr at du fort må grave litt rundt i dokumentasjonen til PySimpleGUI på egenhånd. Noen ganger er det lettest å starte med et eksempel som ligner litt på det man ønsker å få til. PySimpleGUI har blant annet en kokebok med eksempler på hvordan man gjør for skjellige ting, og det finnes en god del eksempler i GitHub repoet til prosjektet. Etter hvert kan det være at man trenger å finne alle detaljer om noen av de komponentene man bruker. Da er det gjerne nyttig å lese om komponenten i referanse-dokumentasjonen. Her finner man blant annet alle parametere man kan bruke for hver komponent.
Prosjektoppgave 🎉
💡 Læringsmål: I dette kapittelet er målet at du bruker mye av det du har lært hittil i et større prosjekt. Vi har samlet noen forslag til prosjekt du kan jobbe med, resten opp til deg! 🌟
Tips
- Prosjektet kan få en viss størrelse, så det kan være lurt å tenke litt på struktur, og være ryddig med koden fra starten av. Start med en tom Python-fil i en ny mappe.
- Bruk Poetry for pakkehåndtering om du bruker eksterne biblioteker.
- Om du har erfaring med versjonering med Git kan det være lurt å pushe kode til et repo ofte. Alternativt kan du lage en sikkerhetskopi av koden jevnlig. Da kan du finne tilbake til en fungerende utgave av programmet om du forviller deg inn i noe som ikke lar seg fikse.
Prosjektideer
📺 Enda mer EPG og GUI
Bruk det du har lært om EPG og GUI til å lage en applikasjon med grafisk brukergrensesnitt som for eksempel har følgende funksjonalitet:
- Viser fram programmet for en gitt dag på en gitt kanal. Bruk en datovelger for å velge dato og en combobox for å velge kanal.
- Lar brukeren velge om data skal hentes fra fil eller API.
- Henter data fra API og lagrer til fil.
- Viser fram bilder og mer informasjon om programmet om man klikker på tittelen. TV-guiden på tv.nrk.no kan være til inspirasjon.
- Kan spille av et program. (Dette er nok et litt vanskelig å få til, og vi vil ikke fortelle for mye om det her, så spør kursveileder om hjelp til å komme i gang.)
🎯 Noe som du trenger til jobb eller fritid
Har du en idé til noe som kunne vært nyttig på jobb eller kanskje til en hobby? Eller kanskje du alt er i gang med et program du vil komme videre med?
🧩 Finn inspirasjon på internett
Internett er selvfølgelig fullt av tips og veiledninger til morsomme og nyttige ting man kan lage. Se om du finner noe interessant blant forslagene under, eller let videre på internett selv.
Spill
- Tic tac toe
- 2048
- Snake
- Norsk utgave av Wordle
- Mange flere ideer ...
https://inventwithpython.com/blog/2012/02/20/i-need-practice-programming-49-ideas-for-game-clones-to-code/
Maskinlæring og data science
- Lær å bruke data-biblioteket Pandas
- 8 Prosjekter med maskinlæring for nybegynnere
- Flere prosjekter med maskinlæring for nybegynnere
Andre nyttige ting
- Bruk Python og maler til å generere dokumenter. For eksempel å lage en mengde brev i Word der innholdet i hovedsak er det samme, men deler er spesifikt for hvert brev.
- Lær å lage en blogg-applikasjon med Web-rammeverket Flask. Bruk det du lærer til å lage din egen web-applikasjon med funksjonaliteten du drømmer om.
- Lag en desktop-applikasjon for stoppeklokke eller timer, for eksempel for å arbeide etter Pomodoro-teknikken
- Eksempel på timer i PySimpleGui
- Eksempel på en enkel Pomodoro-timer som bruker system-notifikasjoner. Man kan ta utgangspunkt i dette programmet, og bygge videre til en Desktop-applikasjon eller lage støtte for konfigurasjon av pause- og arbeidstid.
Løsningforslag
Kapittel 1
1.7 Løkker
Oppgave 1
navneliste = [
"Vibeke",
"Aisha",
"Carlos",
"Vibeke",
"Lise",
"Fatima",
"Per",
"Leyla",
"Oliver",
"Vibeke",
"Henrik",
"Anna",
]
antall_vibeker = 0
for navn in navneliste:
if navn == "Vibeke":
antall_vibeker += 1
print(f"Antall personer som heter Vibeke:", antall_vibeker)
Oppgave 2
quiz = [
{"spørsmål": "Hva er hovedstaden i Norge? ", "svar": "Oslo"},
{"spørsmål": "Hva er hovedstaden i Sverige? ", "svar": "Stockholm"},
{"spørsmål": "Hva er hovedstaden i Danmark? ", "svar": "København"},
]
antall_korrekte_svar = 0
for spørsmålssett in quiz:
svar_fra_bruker = input(spørsmålssett["spørsmål"])
if svar_fra_bruker == spørsmålssett["svar"]:
antall_korrekte_svar += 1
print(f"Antall korrekte svar: ", antall_korrekte_svar)
Oppgave 3
for i in range(1947, 2024, 4):
print(i)
Oppgave 4
handleliste = []
while True:
vare = input("Legg til en vare i handlelisten (eller skriv 'AVSLUTT' for å avslutte): ")
if vare.upper() == 'AVSLUTT':
break
else:
handleliste.append(vare)
print("\nDin handleliste:")
for i, vare in enumerate(handleliste, 1):
print(f"{i}. {vare}")
print(f"Totalt {len(handleliste)} varer i handlelisten.")
Kapittel 2
2.5 Et helt program
import json
import datetime
medier = {
1: "tv",
2: "radio"
}
def to_timedelta(duration):
timepart = duration[2:]
hour_part = timepart.split("H")
if len(hour_part) == 2:
hours = int(hour_part[0])
else:
hours = 0
minutes_part = hour_part[-1].split("M")
if len(minutes_part) == 2:
minutes = int(minutes_part[0])
else:
minutes = 0
return datetime.timedelta(hours=hours, minutes=minutes)
filnavn = "epg.json"
with open(filnavn, "r", encoding="utf-8") as jsonFile:
epg_liste = json.load(jsonFile)
uthenta_program = []
for epg in epg_liste:
kanalnavn = epg["channel"]["title"]
kanal_id = epg["channel"]["id"]
medium = medier[epg["channel"]["sourceMedium"]]
for json_program in epg["entries"]:
kategori_id = json_program["category"]["id"]
tittel = json_program["title"]
varighet = to_timedelta(json_program["duration"])
program = {
"kanalnavn": kanalnavn,
"kanal_id": kanal_id,
"medium": medium,
"kategori_id": kategori_id,
"tittel": tittel,
"varighet": varighet
}
uthenta_program.append(program)
antall_pr_kategori = {}
for program in uthenta_program:
kategori = program["kategori_id"]
if kategori in antall_pr_kategori:
antall_pr_kategori[kategori] += 1
else:
antall_pr_kategori[kategori] = 1
antall_pr_kategori_par = list(antall_pr_kategori.items())
kategorier_med_flest = sorted(antall_pr_kategori_par, key=lambda par: par[1], reverse=True)
for kategori, antall in kategorier_med_flest:
print(f"{kategori.capitalize()}: {antall}")
Kapittel 3
Kapittel 4
4.1 Objektorientert programmering i Python
Oppgave 1
class Kanal:
def __init__(self, navn, programmer):
self.navn = navn
self.programmer = programmer
def vis_programmer(self):
print(f"Programmer på {self.navn}:")
for program in self.programmer:
print(program)
Oppgave 2
nrksuper = Kanal("nrksuper", ["Supernytt", "Minibarna", "Fantus og maskinene"])
nrk2 = Kanal("nrk2", ["Filmavisen", "Med hjartet på rette staden"])
Oppgave 3
print(type(nrksuper))
print(type(nrk2))
Oppgave 4
class Kanal:
def __init__(self, navn, programmer):
self.__navn = navn
self.__programmer = programmer
def legg_til_program(self, program):
self.__programmer.append(program)
def vis_programmer(self):
print(f"Programmer på {self.navn}:")
for program in self.__programmer:
print(program)
Oppgave 5
class Kanal:
...
def __eq__(self, other):
if not isinstance(other, Kanal):
return False
return self.__navn == other.__navn and self.__programmer == other.__programmer
def __str__(self):
return f"Kanal: {self.__navn}, Programmer: {', '.join(self.__programmer)}"
4.2 Dataklasser
Oppgave 1
from dataclasses import dataclass
@dataclass
class Kanal:
navn: str
programmer: list
def legg_til_program(self, program):
self.programmer.append(program)
Oppgave 2
from dataclasses import dataclass
@dataclass
class Kanal:
...
def __post_init__(self):
if self.navn == "" or len(self.programmer) == 0:
Ekstra
Her samler vi innhold som er litt på siden av det kurset handler om, eller som går mer i dybden enn det er plass til i de vanlige kurskapitlene. Underkapitlene er uavhengig av hverandre, så utforsk det som virker interessant og nyttig for deg.
Innhold
Ting du kan gjøre med interaktiv Python
Ved å starte python
-programmet uten noe argument bak, får du opp en interaktiv Python-sesjon.
Her kan du skrive den samme Python-koden som du skriver i .py
-filer, men du får umiddelbar respons på det du skriver.
Når Python-fortolkeren venter på at du skal skrive ei kodelinje, står det >>>
i starten av ei blank linje.
Når du har skrevet ei linje og trykker [ENTER]
, vil fortolkeren kjøre linja og skrive resultatet til terminalen.
Med andre ord trenger du ikke bruke print(...)
for å se hva verdien til ulike variabler eller uttrykk er.
Se kapittel 1.1 for en introduksjon til interaktiv Python.
Her får du noen tips om andre ting du kan gjøre i en interaktiv Python-sesjon.
Teste ut ting
Har du fått en idé som du har lyst til å prøve ut? Med en interaktiv Python-sesjon kan du teste ut ulike ting og få umiddelbar respons på hvordan det virker. Det kan ofte være nyttig å teste litt ut i en interaktiv Python-sesjon først, før du deretter lager den samme koden i ei Python-fil.
Bla i historikken
Du kan bruke [PIL OPP]
og [PIL NED]
til å bla mellom kodelinjene du har kjørt før.
Autofullfør
På Linux og MacOS kan du bruke [TAB]
-tasten til å be Python om å fullføre det du skriver.
Prøv for eksempel å skrive pri
og trykke [TAB]
.
Hvis det er flere valg kan du trykke [TAB]
én gang til for å liste opp alle alternativene.
Interaktiv hjelp
Du kan slå opp dokumentasjon når du er i interaktiv modus!
I det aller første eksemplet i kurset, print("Hallo, verden")
, kjørte vi en funksjon (kodesnutt) som het print
.
Hvis du vil vite hva print
gjør for noe, kan du slå opp dokumentasjonen ved å skrive help(print)
og trykke [ENTER]
.
Resultatet av help(print)
Help on built-in function print in module builtins:
print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
Avhengig av hvor mye dokumentasjon det er, kan du bli tatt til en egen visning.
- På Linux og MacOS får du opp en egen visning der du kan bruke piltastene til å bla.
Trykk på tasten
Q
for å lukke dokumentasjonen og gå tilbake til interaktiv Python. - På Windows blir hele dokumentasjonen skrevet til terminalen hvis det er plass, ellers står det
-- More --
i bunnen. Så lenge det står-- More --
i bunnen kan du trykke på[MELLOMROM]
-tasten for å bla en hel side ned,[ENTER]
for å bla én linje, ellerQ
for å avbryte lesingen og gå tilbake til Python-fortolkeren.
Merk at dette oppslaget bare er ment for utviklere som allerede er kjent med ulike tekniske begreper og Python-syntaksen. Det er ikke forventa at du forstår hva den prøver å si når du akkurat har begynt å lære Python 🙂
Se variabler som er definert
Bruk dir()
til å liste opp alle navn (inkludert definerte funksjoner) som er definert i den nåværende konteksten.
Merk at innebygde funksjoner ikke er tatt med.
Bruk dir(...)
for å liste opp alle attributter som hører til hva enn du oppgir mellom parentesene.
Hvis du for eksempel kjører dir("en tekst")
kan du se alle metodene du kan kjøre på strengen.
Gå i interaktiv modus etter at et skript har kjørt ferdig
Hvis du bruker tilvalget -i
når du kjører et skript, vil Python-fortolkeren tolke skriptet ditt, og deretter åpne interaktiv modus.
I denne interaktive modusen har du tilgang til alle variabler og funksjoner som ble definert i skriptet.
Si at du har et skript, funksjoner.py
, som definerer en funksjon lag_hilsen
, likt eksemplet i kapittel 1.8 om funksjoner.
Da kan du la fortolkeren kjøre funksjonsdefinisjonen og så leke deg med den ferdig definerte funksjonen etterpå:
kurs $> python -i funksjoner.py
>>> lag_hilsen("Jens")
'Hei Jens!'
>>> lag_hilsen(3.14)
'Hei 3.14!'
>>> # Gjør flere tester her
>>> exit()
kurs $>
Morsomheter
Python har noen morsomheter inkludert. Disse er teknisk sett ikke eksklusive for interaktiv Python, men det er jo et ypperlig sted å prøve dem ut:
-
import this
-
import antigravity
(merk:print "Hello, world!"
er gammel syntaks og er erstatta avprint("Hello, world!")
i Python 3) -
For deg som har programmert i Java, JavaScript eller tilsvarende språk som bruker "braces" (
{
og}
) til å markere starten og slutten på kodeblokker, og ønsker deg dette i stedet for innrykk i Python:from __future__ import braces
(
from __future__ import ...
brukes vanligvis til å aktivere frivillig funksjonalitet som kan komme til å bli standard i en seinere Python-versjon)
Tips og triks om terminalen
Denne sida tar med diverse temaer som kan være nyttige hvis du er ny bruker av terminal.
Innholdsfortegnelse
- Hva er en terminal?
- Alle terminaler er ikke like
- Navigere filsystemet
- Vanlige kommandoer
- Vanlig funksjonalitet i skall
- Konvensjoner for program du kjører i terminalen
- Temaer som ikke er dekket av denne gjennomgangen
- Videre lesning
Hva er en terminal?
Terminalen/kommandolinja, på engelsk ofte kalt command line, er en tekstbasert måte å starte programmer på. Vi bruker den ofte når vi lager dataprogram.
I terminalen skriver du inn kommandoer etter hverandre.
Det gjør du ved å skrive inn en kommando og trykke [ENTER]
.
Hver kommando du kjører ber datamaskinen om å gjøre noe.
Hvis du ikke får noen feilmelding, har det som regel gått bra – terminalprogram pleier ikke å si fra at ting er OK.
Alle terminaler er ikke like
Det finnes mange forskjellige typer shell. Et skall er programmet som du snakker med når du jobber i terminalen.
Hvilket skall du bruker, avgjør hvilken avansert funksjonalitet du har tilgang til. Den grunnleggende funksjonaliteten for å starte et program er mer eller mindre helt lik for alle skall.
Skallet bash
er velkjent og mye brukt, spesielt på Linux og Mac før macOS Catalina.
Fra og med macOS Catalina er zsh
det innebygde skallet.
På Windows er cmd
og Powershell
vanlige, der sistnevnte er den mest avanserte.
Men de fungerer ganske annerledes fra andre skall.
Du bør vurdere å installere bash
eller oppsøke en annen innføring i Powershell
.
Vi går litt fort fram i denne gjennomgangen. Du kan søke etter «Introduction to Bash command line» for guider som går litt grundigere til verks, for eksempel Programming Historian's Introduction to the Bash Command Line eller Ryans Linux-introduksjon (det meste gjelder også på andre operativsystem så lenger du bruker Bash).
Navigere filsystemet
Working Directory
Du står til enhver tid i ei mappe, kalt working directory. Du kan sammenlikne det et vindu av en filutforsker, som til enhver tid har én mappe åpen.
Med kommandoen pwd
kan du Print Working Directory. Da ser du hvor du står.
Bruk kommandoen cd MAPPE
for å bytte (Change Directory) til MAPPE.
Absolutte filstier
Uansett hvor du står, kan du alltid referere til en hvilken som helst fil som ligger hvor som helst. Da bruker du absolutt filsti. Absolutte filstier starter med en skråstrek, bortsett fra Windows hvor de også kan starte med stasjonen og kolon.
Eksempler på absolutte filstier på Linux/Unix:
/home/n123456/Documents
/etc/hosts
/etc/hostname
Windows:
C:\Users\n123456\Desktop
D:\Backup\2019\03\14.zip
Absolutte filstier avhenger ikke av hvilken mappe du står i, de er alltid det samme.
Hjemmemappa
De aller fleste skall opererer med ei spesiell mappe kalt hjemmemappa, eller home på engelsk.
På Linux og Unix pleier den å være /home/n123456
, på Windows C:\Users\n123456
.
Skal du referere til hjemmemappa di, kan du som regel bruke tilde-tegnet, ~
.
På norsk tastatur holder du inne [Alt Gr]
, trykker tasten mellom Å
og [ENTER]
,
slipper [Alt Gr]
og trykker på [MELLOMROM]
.
Eksempel:
~/Desktop
~/Documents
Merk at tilde blir utvidet av skallet. De blir dermed gjort om til absolutte filstier før noe annet program begynner å lese dem.
Du kan alltids returnere til hjemmemappa ved å kjøre kommandoen cd
uten argument.
Relative filstier
Relative filstier tar utgangspunkt i mappa du står i. De starter med navnet på ei fil eller ei mappe i den mappa.
Det ligger to spesielle filer i hver mappe:
.
er en referanse til den mappa...
er en referanse til foreldermappa.
Si at du står i /home/n123456/kurs/ekstra
.
Tabellen nedenfor viser hvordan du kan bygge en relativ filsti for ulike destinasjoner:
Absolutt filsti | Relativ filsti fra /home/n123456/kurs/ekstra |
---|---|
/home/n123456/kurs/ekstra/terminal.md | terminal.md |
/home/n123456/kurs/ekstra | . |
/home/n123456/kurs/ekstra | ./././././. |
/home/n123456/kurs | .. |
/home/n123456/kurs | ../../kurs/../kurs/../kurs/../kurs |
/home/n123456/kurs/kap1 | ../kap1 |
/home/n123456/kurs/kap1/README.md | ../kap1/README.md |
/home/n123456 | ../.. |
/home | ../../.. |
Jokertegn
Skallet lar deg bruke jokertegn for å velge flere filer ut fra likheter i filstien.
Stjerna, *
, kan brukes for å matche hva som helst i en filsti.
Alle treffene vil bli lagt etter hverandre, med mellomrom, før de blir sendt til programmet som skal kjøres.
For eksempel så kan du konkattenere alle filene i mappa du står i som slutter på .py
:
kurs $> cat *.py
Hvis du har 1.py
, 2.py
og 3.py
liggende i mappa du er i, så vil skallet ditt gjøre om kommandoen din til:
cat 1.py 2.py 3.py
før den kjører cat
.
Vanlige kommandoer
Noen av disse kommandoene er utilgjengelige i PowerShell, som fungerer ganske annerledes fra andre skall.
cd MAPPENAVN
: Change Directory. Lar deg endre hvilken mappe du står i. Kan sammenliknes med å dobbelttrykke på ei mappe i filutforskeren.cd
: Bytt til hjemmemappa.cd -
: Bytt til den forrige mappa du sto i. Historikken går bare én mappe bakover i tid.pwd
: Print Working Directory. Skriv til terminalen hvilken mappe du står i.ls
: LiSt. Vis innholdet i mappa du står i.ls MAPPE
: Vis innholdet av MAPPE.cat FIL
: conCATenate. Vis innholdet av FIL. Du kan liste opp flere filer for å konkattenere dem sammen.mv FRA TIL
: MoVe. Bytt navn på fila eller mappa FRA, sånn at den heter TIL. Du kan også flytte den til et annet sted.cp KILDE MÅL
: CoPy. Lag en kopi av KILDE som heter MÅL.rm FIL
: ReMove. Slett FIL permanent.man KOMMANDO
: MANual. Vis hjelpeinformasjon for KOMMANDO. Tastq
for å avslutte.grep SØK FIL
: Vis alle tilfeller av SØK i FIL.grep -r SØK MAPPE
: Vis alle tilfeller av SØK i filer i MAPPE.nano FIL
: Rediger FIL i en enkel editor. Du kan holde inne[CTRL]
og trykke en tast for å lagre eller avslutte, tastene står nederst.^O
betyr[CTRL]
+O
,M-U
betyr[ALT]
+U
.
Vanlig funksjonalitet i skall
Kjør et program
Den første delen av enhver kommando bestemmer hvilket program du skal kjøre.
Når vi kjører Python, skriver vi alltid python
først i kommandoen.
Da er det Python-fortolkeren som kommer til å kjøre.
Kommandolinje-argumenter
Etter navnet på programmet kan du legge til argumenter som vil bli gitt til programmet.
Vi kaller dem for kommandolinje-argumenter (command-line arguments).
Når vi kjører Python-kode vi har skrevet, skriver vi navnet på .py
-fila etter python
, men med et mellomrom i mellom.
Python-fortolkeren vet at filnavn du gir til den skal tolkes som Python-skript som skal kjøres.
For eksempel så har cp hei.txt hallo.txt
to kommandolinjeargument som blir gitt til cp
:
hei.txt
hallo.txt
Hvis en filsti skal inneholde mellomrom, må du bruke hermetegn rundt argumentet sånn at skallet oppfatter det som ett argument og ikke flere. For eksempel:
cp "hilsen hei.txt" "hilsen hallo.txt"
har to kommandolinjeargument som blir gitt til cp
:
hilsen hei.txt
hilsen hallo.txt
Glemmer du hermetegn, får du:
cp hilsen hei.txt hilsen hallo.txt
som har fire kommandolinjeargument som blir gitt til cp
:
hilsen
hei.txt
hilsen
hallo.txt
Det blir feil.
I kapittel 3.2 lærer du om hvordan Python-skriptet ditt kan dra nytte av kommandolinjeargument.
Avbryte kjørende program
Du kan avslutte de fleste program med [CTRL]
+ C
.
Noen program bruker den tastekombinasjonen for noe annet. De kan gjerne avsluttes på andre måter:
less
(for å skrolle gjennom tekst) ogman
avsluttes ved å trykkeQ
nano
avsluttes med[CTRL]
+X
vim
(kraftig, men uvant teksteditor) avsluttes ved å trykke[ESC]
, taste:q!
(lagrer ikke endringer) og[ENTER]
- Program som leser fra terminalen kan ofte avsluttes med
[CTRL]
+D
. Da gir du beskjed om at du er ferdig med å skrive - Siste utvei er
[CTRL]
+Z
for å sette programmet på pause, etterfulgt avkill -9 %1
Autofullfør
Når du skriver kommandoer i terminalen,
kan du bruke [TAB]
-tasten til å fullføre argumentet du skriver på.
Hvis det du har skrevet er nok til å vite hvilken fil du tenker på,
kan du fylle ut resten med ett trykk.
Er det flere filer, må du gjerne trykke flere ganger for å få forslag eller gå gjennom forslagene.
Dette er veldig nyttig siden du ofte vil skrive filnavn i kommandoer. Du kan bruke autofullfør både når du skal skrive hvilket skript Python-fortolkeren skal kjøre, og når du skal fortelle skriptet hvilken fil du vil lese fra eller skrive til.
Historikk
Bruk [PILTAST OPP]
og [PILTAST NED]
til å bla gjennom historikken.
Vil du kjøre den forrige kommandoen din én gang til,
kan du trykke [PILTAST OPP]
én gang, etterfulgt av [ENTER]
.
Du kan også endre på kommandoen etter at du har bladd deg opp til den,
for eksempel hvis du vil kjøre det samme med en liten endring.
Konvensjoner for program du kjører i terminalen
Her er noen konvensjoner som terminalprogram pleier å følge. Men du har selvfølgelig ingen garantier for at alle program følger disse.
Når du skriver egne program, bør du etterstrebe å overholde disse konvensjonene. Det gjør det enklere for andre å ta i bruk programmet ditt. Kapittel 3.2 om kommandolinjeargumenter går i detalj på hvordan du får til det.
Korte tilvalg/flagg
Kommandoers virkemåte kan justeres med tilvalg (options). De starter alltid med bindestrek. Korte tilvalg (short options) er alltid ett tegn lange.
For eksempel kan du få ls
til å vise flere detaljer med -l
hvis du bruker bash
som skall:
kurs $> ls -l
total 1128
-rw-r--r-- 1 n123456 n123456 1008 Mar 27 15:59 README.md
-rw-r--r-- 1 n123456 n123456 622 Feb 13 13:08 book.toml
...
-rw-r--r-- 1 n123456 n123456 1094001 Feb 13 13:08 mermaid.min.js
...
Du kan vise filstørrelser med suffiks for kilo, mega, og så videre med -h
:
kurs $> ls -l -h
...
-rw-r--r-- 1 n123456 n123456 1.1M Feb 13 13:08 mermaid.min.js
...
Når du gir flere korte tilvalg, kan du kombinere dem ved å bruke én bindestrek etterfulgt av alle tilvalgene uten bindestrek, for eksempel:
kurs $> ls -lh
...
-rw-r--r-- 1 n123456 n123456 1.1M Feb 13 13:08 mermaid.min.js
...
Tilvalg skal som regel plasseres rett etter navnet på programmet du kjører, men rekkefølgen på dem spiller ingen rolle.
Tilvalgene som er vist i dette delkapittelet tar ikke inn noen tilhørende verdi. De kalles også for flagg.
Korte tilvalg med tilhørende verdi
Noen tilvalg tar inn en verdi. Den kan du angi som det neste kommandolinjeargumentet.
grep
, programmet som lar deg søke etter en tekst i ei fil, printer vanligvis bare de linjene som har søkestrengen i seg.
Du kan få den til å skrive linjene som kommer før og etter med tilvalget -C <antall linjer>
. For eksempel:
kurs $> grep -C 2 Python README.md
...
Også disse tilvalgene kan kombineres med bare én bindestrek, så lenge tilvalget som skal ta inn en verdi er den siste i gruppa:
kurs $> grep -iC 2 Python README.md
...
-i
er her et flagg som sier at søket skal ignorere små og store bokstaver.
Lange tilvalg/flagg
Lange tilvalg starter alltid med to bindestreker, etterfulgt av hele ord eller fraser.
Ett og samme tilvalg har ofte en kort og en lang variant.
For eksempel kan du bruke enten -a
eller --all
for å vise skjulte filer med ls
.
Når du til daglig sitter i terminalen er det praktisk å bruke korte tilvalg, men hvis du skriver ned kommandoer i et skript eller i en guide, bør du bruke lange tilvalg sånn at leseren lettere forstår hva de gjør.
Lange tilvalg med tilhørende verdi
Den tilhørende verdien til et langt tilvalg kan gis som et nytt kommandolinjeargument, likt som for korte tilvalg. Men du kan også gi verdien ved å bruke et likhetstegn.
For eksempel:
kurs $> ls --format=long --all --reverse --sort time --human-readable
Her bruker --format
likhetstegn og --sort
mellomrom, men det kunne like gjerne vært omvendt,
eller begge kunne brukt den samme måten.
Få hjelpeinformasjon
Legg til flagget --help
på en kommando for å få en hjelpetekst som forklarer hva programmet gjør, og hvilke tilvalg det har.
Noen kommandoer støtter kun det korte tilvalget -h
, mens andre kommandoer bruker -h
for noe annet enn hjelp.
Signalisering av feil
Alle program har en avslutningskode, exit code på engelsk, som blir satt når programmet avslutter. Denne blir brukt for å kommunisere hvordan det gikk med programmet. For eksempel kan et skript som består av flere andre skript velge å avbryte kjøringa hvis et av programmene flagger at noe gikk galt.
Hvis alt gikk bra, så skal programmet avslutte med avslutningskode 0
.
Alle andre avslutningskoder indikerer at noe gikk galt.
Du vil typisk bruke 1
til å indikere at noe galt har skjedd.
Python-skript trenger som regel ikke tenke på dette.
Hvis skriptet kjører ferdig uten at det krasjer, vil det avslutte med avslutningskode lik 0
.
Hvis skriptet derimot krasjer, avslutter det med avslutningskode lik 1
.
Det eneste måtte være hvis du avslutter skriptet tidlig med sys.exit
,
som tar inn avslutningskoden som et argument.
Temaer som ikke er dekket av denne gjennomgangen
- Alt som har med strømmer å gjøre
- Piping av output fra et program til et annet
- Omdirigering, redirecting, av output eller input til eller fra ei fil
- Input- og output-strømmene til alle program:
stdout
,stderr
ogstdin
- Prosesshåndtering/kjøre ting i bakgrunnen
- Programmering i skall (skripting, if, while, for, ...)
- Variabler
- Vim! 😁
- Powershell