5. Moment05 - Funktioner och datalagring

Info

Till detta moment finns en sida med lösningsförslag och videoklipp.

För de elever som vill utmanas ytterligare med fler och/eller svårare uppgifter hittar dessa här.

I de tidigare momenten har vi arbetat med variabler, villkor, loopar och datastrukturer. Nu tar vi nästa steg: vi ska bygga program som är uppdelade i funktioner och som kan spara och läsa data från filer.

Hittills har våra program “glömt” all information när de avslutas. I detta moment ska vi lära oss hur data kan lagras i en fil och läsas in igen nästa gång programmet körs.

Momentet fokuserar på teknik och struktur:

  • hur program delas upp i funktioner
  • hur data lagras i textfiler
  • hur strukturerad data läses in och skrivs tillbaka
  • hur listor används som grundstruktur

Målet är att du ska kunna bygga program som både är strukturerade och kan hantera data över tid.

5.1 Funktioner – struktur i program

I tidigare moment har vi skrivit program där all kod ligger “på rad”. Det fungerar, men när programmen växer blir de svårare att läsa, svårare att förstå och svårare att förändra.

En funktion är en namngiven koddel som samlar instruktioner som hör ihop. Istället för att skriva allt i ett enda långt block delar vi upp programmet i mindre delar med tydliga namn.

Funktioner hjälper oss att:

  • undvika att skriva samma kod flera gånger
  • göra programmet lättare att läsa
  • dela upp ett stort problem i mindre delar
  • kunna återanvända kod på flera ställen

Tänk på ett program som en maskin. Om alla delar ligger huller om buller blir den svår att förstå. Om varje del har ett namn och ett tydligt ansvar blir programmet mer strukturerat.

I detta avsnitt ska vi lära oss hur man skapar och använder funktioner för att bygga mer strukturerade program.

5.1.1 Skapa och anropa en funktion

En funktion skapas med def. Koden i funktionen körs först när funktionen anropas.

Det är viktigt att själva funktionen är definierad innan du försöker anropa den. Python läser koden uppifrån och ner. Om du anropar en funktion innan Python hunnit läsa dess definition får du ett fel.

def hälsa():
    print("Hej!")

hälsa()

Utskrift

Hej!
hälsa()

def hälsa():
    print("Hej!")

Utskrift

NameError: name 'hälsa' is not defined

Om du får detta fel betyder det oftast att du anropar en funktion som Python inte känner till ännu. Kontrollera då att:

  • funktionen ligger ovanför anropet (i samma fil)
  • eller att du importerar rätt fil om funktionen ligger i en modul
def hälsa():
    print("Hej!")

hälsa()
hälsa()

Utskrift

Hej!
Hej!

Övning 5.1a – egen funktion

Skapa en funktion som heter skriv_titel() som skriver ut en titelrad, t.ex.: ===== MIN APP =====

Anropa funktionen minst tre gånger i programmet.

Lösningsförslag

def skriv_titel():
    print("===== MIN APP =====")

skriv_titel()
skriv_titel()
skriv_titel()

5.1.2 Parametrar – skicka in värden

Parametrar gör funktionen flexibel: samma funktion kan användas med olika värden. När du anropar funktionen skickar du in värden som kallas argument.

def hälsa_namn(namn):
    print("Hej " + namn + "!")

hälsa_namn("Anna")
hälsa_namn("Erik")

Utskrift

Hej Anna!
Hej Erik!

5.1.2.1 Defaultvärden

En parameter kan ha ett defaultvärde. Det betyder att parametern får ett värde automatiskt om du inte skickar in något värde vid anropet.

def summera(a, b=10):
    print(a + b)

summera(5, 2)   # b blir 2
summera(5)      # b blir 10 (default)

Utskrift

7
15

Övning 5.1b – två parametrar

Skapa en funktion multiplicera(a, b) som skriver ut produkten av a och b.

Anropa funktionen med minst tre olika exempel.

Lösningsförslag

def multiplicera(a, b):
    print(a * b)

multiplicera(3, 4)
multiplicera(10, 2)
multiplicera(7, 5)

5.1.2.2 Namngivna argument (keyword arguments)

När du anropar en funktion brukar du skicka in argument i rätt ordning. Då kopplas argumenten till parametrarna positionellt (först till första parametern, sedan till nästa, osv.).

def summera(a, b, c=1, d=0):
    print(a + b + c + d)

summera(3, 7, 2, 4)       # Utskrift: 16

Du kan också koppla argument till parametrar genom att skriva parameterns namn vid anropet. Detta kallas namngivna argument (på engelska keyword arguments).

def summera(a, b, c=1, d=0):
    print(a + b + c + d)

summera(a=3, b=7, d=4)     # Utskrift: 15

I exemplet ovan får c sitt defaultvärde (1) eftersom vi inte skickade in något värde för c.

Viktig regel: Om du blandar positionella och namngivna argument måste de positionella komma först.

# Rätt:
summera(3, 7, d=4)

# Fel (ger SyntaxError):
# summera(a=3, 7, d=4)

Övning 5.1c – namngivna argument

Skapa en funktion rabatt(pris, procent=10) som returnerar priset efter rabatt.

Rabatten räknas så här: pris - (pris * procent / 100)

  1. Anropa funktionen med bara pris (så att defaultvärdet används).
  2. Anropa funktionen igen, men denna gång med namngivet argument där du ändrar procenten.

Lösningsförslag

def rabatt(pris, procent=10):
    return pris - (pris * procent / 100)

# Defaultvärde (10%)
pris1 = rabatt(200)
print(pris1)

# Namngivet argument (25%)
pris2 = rabatt(200, procent=25)
print(pris2)

5.1.2.3 Vad kan skickas in som argument?

När vi anropar en funktion behöver argumenten inte vara hårdkodade värden som 5 eller "Anna". Vi kan också skicka in värden från variabler, uttryck och listor.

def skriv_summa(a, b):
    print(a + b)

x = 7
tal_lista = [10, 20, 30]

skriv_summa(3, 4)              # hårdkodade värden
skriv_summa(x, 5)              # variabel
skriv_summa(2 + 3, 4 * 2)      # uttryck
skriv_summa(tal_lista[0], 5)   # värde hämtat från lista

Det viktiga är att det som skickas in blir ett värde som passar funktionen.

Nästa steg

Det går också att skicka in resultatet från en annan funktion som argument, till exempel skriv_summa(len(tal_lista), 5). Det återkommer vi till senare.

5.1.3 Return – skillnad på print och return

Hittills har vi använt print() för att visa resultat. Men ibland behöver vi använda resultatet vidare i programmet. Då räcker inte print() – då behöver vi return.

  • print() – visar något på skärmen
  • return – skickar tillbaka ett värde till den som anropade funktionen

Anta att vi vill räkna ut den totala arean av tre rektanglar. Då behöver vi kunna använda varje area vidare i programmet.

def area_rektangel(bredd, hojd):
    return bredd * hojd

area1 = area_rektangel(3, 5)
area2 = area_rektangel(4, 2)
area3 = area_rektangel(6, 3)

total_area = area1 + area2 + area3

print(f"Totala arean är {total_area}")

Om funktionen istället hade använt print() hade vi inte kunnat spara värdena i variabler och summera dem.

5.1.3.1 Vanligt misstag: Varför skrivs "None" ut?

När man är ny på funktioner händer det ofta att man blandar ihop print och return. Titta på detta exempel:

def a():
    print("a")

def b():
    return "b"

a()
print(a())

b()
print(b())

Utskrift

a
a
None
b

Vad händer rad för rad?

  • Rad 7: a() - Funktionen körs och skriver ut "a". Den returnerar inget värde, alltså returneras automatiskt None.
  • Rad 8: print(a()) - Först körs a() igen → den skriver ut "a". Sedan returnerar funktionen None. print() skriver då ut detta värde → därför ser vi None.
  • Rad 10: b() - Funktionen returnerar strängen "b". Men eftersom vi inte skriver ut värdet händer inget synligt på skärmen.
  • Rad 11: print(b()) - Funktionen körs och returnerar "b". Detta värde skickas in i print(), som då skriver ut "b".

Viktig princip:

  • En funktion utan return returnerar automatiskt None.
  • Att anropa en funktion är inte samma sak som att skriva ut dess returvärde.

Kom ihåg:
Om du vill använda resultatet vidare → använd return.
Om du bara vill visa något → använd print().

Övning 5.1d – return istället för print

Skapa en funktion addera(a, b) som returnerar summan.

Spara svaret i en variabel och skriv ut det efteråt.

Lösningsförslag

def addera(a, b):
    return a + b

svar = addera(10, 7)
print(svar)

Uppgift: m05u01

Du ska göra nästan samma sak i två olika funktioner för att se skillnaden på print och return.

Del 1 – Funktion som skriver ut

  • Skapa en funktion summera_print(a, b).
  • Funktionen ska räkna ut summan och skriva ut resultatet.

Del 2 – Funktion som returnerar

  • Skapa en funktion summera_return(a, b).
  • Funktionen ska räkna ut summan och returnera resultatet.

Del 3 – Huvudprogram

  • Anropa båda funktionerna med valfria tal.
  • Resultatet från summera_return ska sparas i en variabel.
  • Skriv sedan ut variabeln på ett annat sätt, t.ex. med en f-string: print(f"Summan blev {svar}").

Tips: Målet är att du ska se att return gör att du kan använda resultatet vidare i programmet.

5.1.4 Funktioner i separat fil (moduler)

När program växer blir det rörigt att ha all kod i samma fil. Därför kan vi dela upp programmet i flera filer.

En vanlig struktur är:

  • main.py – styr programmet
  • funktioner.py – innehåller funktioner

Detta gör programmet mer strukturerat och lättare att underhålla.

funktioner.py

def skriv_meny():
    print("1. Säg hej")
    print("2. Avsluta")

def hamta_val():
    return input("Välj: ")

def hantera_val(val):
    if val == "1":
        print("Hej!")
    elif val == "2":
        print("Avslutar...")
    else:
        print("Ogiltigt val")

main.py

import funktioner

funktioner.skriv_meny()
val = funktioner.hamta_val()
funktioner.hantera_val(val)

Observera att:

  • Filerna måste ligga i samma mapp.
  • Vi skriver import funktioner (utan .py).
  • Vi anropar funktionerna via filnamnet.

Import på olika sätt

I detta moment använder vi:

import funktioner

Då anropar vi funktionerna så här:

funktioner.skriv_meny()

Ett annat sätt är:

from funktioner import *

Då kan vi skriva:

skriv_meny()

Detta är kortare, men det finns en viktig skillnad:

  • Med import funktioner ser vi tydligt var funktionen kommer ifrån.
  • Med from ... import * importeras alla funktioner direkt in i programmet. Det kan göra det svårare att förstå var en funktion är definierad.
  • Om två filer innehåller funktioner med samma namn kan de skriva över varandra.

I denna kurs använder vi därför import med modulnamn eftersom det gör programmet tydligare och mer strukturerat.

Övning 5.1e – dela upp program i två filer

Skapa ett program med en meny och minst tre val.

  1. Lägg alla funktioner i funktioner.py.
  2. Anropa funktionerna via import.

Lösningsförslag

# funktioner.py
def skriv_meny():
    print("1. Dubbel tal")
    print("2. Avsluta")

def dubbla(tal):
    return tal * 2


# övning51e.py
import funktioner

funktioner.skriv_meny()
val = input("Välj: ")

if val == "1":
    tal = int(input("Ange tal: "))
    resultat = funktioner.dubbla(tal)
    print(resultat)
elif val == "2":
    print("Avslutar...")

Uppgift: m05u02

Nu ska du ta ditt program från m05u01 och dela upp det i två filer. Målet är att du ska träna på att importera funktioner från en annan fil.

Del 1 – Skapa två filer

  • m05u02.py – styr programmet
  • funktioner.py – innehåller dina funktioner

Del 2 – Flytta funktionerna

  • Flytta summera_print(a, b) och summera_return(a, b) till funktioner.py.
  • Om du har fler funktioner (t.ex. meny) ska även de ligga i funktioner.py.

Del 3 – Importera och använd funktionerna

  • I m05u02.py ska du importera filen med: import funktioner
  • Anropa funktionerna som: funktioner.summera_print(...) och funktioner.summera_return(...)

Del 4 – Kontrollera att allt fungerar

  • Programmet ska fungera på samma sätt som i m05u01.
  • Testa minst två olika anrop till båda funktionerna.

Fördjupning (frivillig)

Skapa två moduler: rektangel.py och cirkel.py. Lägg funktioner för area och omkrets i respektive fil. Importera dem i m05u02.py med:

import rektangel
import cirkel

Anropa sedan funktionerna med modulnamn, t.ex. rektangel.area(...) och cirkel.area(...). På så sätt kan du ha samma funktionsnamn (area) i båda filerna utan att det blir konflikt.

Extra utmaning: importera med nytt namn (alias), t.ex. from rektangel import area as area_rektangel.

5.1.5 Dokumentation av funktioner

När program blir större är det viktigt att andra (och du själv i framtiden) förstår vad en funktion gör.

Därför behöver funktioner dokumenteras. Det kan göras på olika nivåer – från enkla kommentarer till så kallade docstrings.

5.1.5.1 Enkla kommentarer

Den enklaste formen av dokumentation är en kommentar med #. Kommentaren beskriver vad funktionen gör.

def area_rektangel(bredd, hojd):
    # Beräknar arean av en rektangel
    return bredd * hojd

Kommentarer är bra, men de är mest till för människor som läser koden.

5.1.5.2 Docstrings

En docstring är en dokumentationssträng som skrivs direkt under funktionshuvudet med tre citationstecken (""").

Den beskriver:

  • vad funktionen gör
  • vilka parametrar den tar emot
  • vad den returnerar
def area_rektangel(bredd, hojd):
    """
    Beräknar arean av en rektangel.

    Parametrar:
        bredd (int eller float): Rektangelns bredd
        hojd (int eller float): Rektangelns höjd

    Returnerar:
        int eller float: Rektangelns area
    """
    return bredd * hojd

Docstrings är standard i Python och gör det lättare att förstå och använda funktioner.

5.1.5.3 PyCharm och automatisk dokumentation

I PyCharm kan du automatiskt skapa en docstring.

  • Skriv """ direkt under funktionshuvudet.
  • Tryck Enter.
  • PyCharm skapar då en mall med parametrar och returvärde.

Du kan sedan fylla i beskrivningarna själv.

Övning 5.1f – dokumentera en funktion

Välj en funktion du har skrivit tidigare i momentet.

  1. Lägg till en enkel kommentar som beskriver vad funktionen gör.
  2. Lägg sedan till en docstring med beskrivning av parametrar och returvärde.

Lösningsförslag

def addera(a, b):
    """
    Returnerar summan av två tal.

    Parametrar:
        a (int eller float): Första talet
        b (int eller float): Andra talet

    Returnerar:
        int eller float: Summan av talen
    """
    return a + b

Att dokumentera sina funktioner är en viktig del av att skriva tydlig och professionell kod.

Uppgift: m05u03

I denna uppgift bygger du vidare på m05u02. Målet är att träna på att dokumentera funktioner så att det blir tydligt för någon annan (och dig själv) vad funktionerna gör, vilka parametrar de tar emot och vad de returnerar.

Del 1 – Kommentera dina funktioner

  • Öppna funktioner.py från m05u02.
  • Lägg till en kort kommentar som beskriver syftet med varje funktion.

Del 2 – Lägg till docstrings

  • Lägg till en docstring (""" ... """) i varje funktion.
  • Docstringen ska minst beskriva:
    • vad funktionen gör
    • vilka parametrar som finns
    • vad funktionen returnerar (om den returnerar något)

Del 3 – Testa att läsa dokumentationen

  • Kör m05u02.py och kontrollera att programmet fortfarande fungerar.
  • Testa att skriva help(funktioner.summera_return) i main.py och kör programmet.
  • Du ska kunna läsa din docstring i terminalen.

Tips

  • Om en funktion bara skriver ut saker kan du skriva Returnerar: None i docstringen.
  • I PyCharm kan du skriva """ och trycka Enter för att få en mall.

Uppgift: m05u04

Detta är en ny uppgift (den bygger inte på m05u01–m05u03). Du ska från start skapa ett program som:

  • är uppdelat i två filer (modul)
  • använder funktioner med parametrar och return
  • är dokumenterat (kommentar + docstring)
  • har en liten klurighet kopplad till defaultvärde och None

Idé: Geometri-kalkylator

Du ska räkna ut area och omkrets för rektanglar. Klurigheten är att om höjden inte anges, ska rektangeln räknas som en kvadrat (då är höjd = bredd).

Del 1 – Skapa två filer

  • m05u04.py
  • geometri.py

Del 2 – Skapa funktioner i geometri.py

Skapa dessa funktioner (med docstring och en enkel kommentar i varje funktion):

  • area_rektangel(bredd, hojd=None)
    Returnerar arean. Om hojd är None ska den behandlas som en kvadrat.
  • omkrets_rektangel(bredd, hojd=None)
    Returnerar omkretsen. Om hojd är None ska den behandlas som en kvadrat.

Del 3 – Använd funktionerna i m05u04.py

  • Importera med: import geometri
  • Läs in bredd och höjd från användaren.
  • Om användaren bara anger bredd (och lämnar höjd tom) ska programmet skicka None som höjd.
  • Skriv ut area och omkrets.

Del 4 – Test

  • Testa en rektangel (både bredd och höjd).
  • Testa en kvadrat (bara bredd).

Extra utmaning (frivillig)

Testa att anropa med namngivna argument i main.py, t.ex.: geometri.area_rektangel(bredd=5, hojd=2) eller geometri.area_rektangel(bredd=5).

5.2 Filhantering

I tidigare moment har våra program bara arbetat med data medan programmet körs. När programmet avslutas försvinner all information.

I många program behöver data sparas så att den kan användas igen nästa gång programmet startas. Exempel kan vara:

  • sparade inställningar
  • listor med namn eller resultat
  • loggar eller mätvärden

Ett enkelt sätt att lagra data är att använda filer. En fil kan innehålla text som programmet läser in och arbetar vidare med.

I detta avsnitt ska vi arbeta med tre saker:

  • läsa data från en fil
  • skriva data till en fil
  • lagra strukturerad data i filer

Vi börjar med det enklaste fallet: en fil där varje rad innehåller ett värde.

5.2.1 Läsa från fil

För att läsa från en fil använder vi funktionen open(). Det vanligaste sättet är att använda konstruktionen with open().

Fördelen med with open() är att filen automatiskt stängs när blocket är färdigt. Du behöver alltså inte själv skriva någon extra kod för att stänga filen.

namn.txt

Anna
Erik
Sara
with open("namn.txt") as fil:
    for rad in fil:
        print(rad)

Utskrift

Anna
 
Erik
 
Sara

I exemplet ovan läses filen rad för rad i en loop.

Varje rad i filen är en sträng och innehåller redan ett radbyte (\n) i slutet.

När vi använder print() läggs ytterligare ett radbyte till. Därför visas en tom rad mellan varje namn i utskriften.

Ofta vill man ta bort radbrytningen i slutet av raden innan man använder datan. Det görs med funktionen strip().

with open("namn.txt") as fil:
    for rad in fil:
        namn = rad.strip()
        print(namn)

Var ska filen ligga?

I våra exempel antar vi att textfilen ligger i samma mapp som Pythonprogrammet.

Om filen ligger i en annan mapp måste man ange hela sökvägen till filen. Det kommer vi inte att arbeta med i detta moment.

Om filen inte finns i rätt mapp får du ett felmeddelande som kan se ut så här:

FileNotFoundError

Övning 5.2a – läsa en fil med namn

Skapa en textfil som heter namn.txt.

Skriv minst tre namn i filen, ett namn per rad.

namn.txt

Anna
Erik
Sara

Skriv ett Pythonprogram som:

  1. läser filen rad för rad
  2. tar bort radbrytningen med strip()
  3. skriver ut varje namn

Lösningsförslag

with open("namn.txt") as fil:
    for rad in fil:
        namn = rad.strip()
        print(namn)

I många fall vill vi inte bara skriva ut det vi läser in. Ofta vill vi istället lagra datan i en lista så att programmet kan arbeta vidare med den.

provpoäng.txt

57
33
24
tal_lista = []

with open("provpoäng.txt") as fil:
    for rad in fil:
        tal = int(rad.strip())
        tal_lista.append(tal)

print(tal_lista)

Tomma rader i filer

Om en textfil innehåller tomma rader kan det skapa problem, särskilt om vi försöker konvertera raden till ett tal med int().

Ett enkelt sätt att undvika detta är att hoppa över tomma rader:

for rad in fil:
    rad = rad.strip()

    if rad == "":
        continue

    tal = int(rad)

Då ignoreras tomma rader i filen.

Övning 5.2b – läsa in tal till en lista

Skapa en fil som heter temperaturer.txt.

Skriv minst fyra heltal i filen, ett tal per rad.

temperaturer.txt

18
21
16
23

Skriv sedan ett program som:

  1. läser filen rad för rad
  2. konverterar varje rad till ett heltal
  3. lagrar talen i en lista
  4. skriver ut listan

Lösningsförslag

temperaturer = []

with open("temperaturer.txt") as fil:
    for rad in fil:
        tal = int(rad.strip())
        temperaturer.append(tal)

print(temperaturer)

Uppgift: m05u05

Skapa en fil som heter tal.txt och skriv minst fem heltal i filen, ett tal per rad.

tal.txt

12
7
19
3
15

Skriv ett program som:

  • läser alla tal från filen
  • konverterar varje rad till ett heltal
  • lagrar talen i en lista
  • skriver ut listan
  • skriver ut summan av alla talen
  • skriver ut största och minsta talet
  • skriver ut medelvärdet
  • räknar hur många tal som är större än 10
  • skriver ut talen i sorterad ordning

Medianvärdet ska beräknas i en egen funktion.

  • Skapa funktionen median(tal_lista)
  • Funktionen ska ta listan som parameter
  • Funktionen ska returnera medianvärdet

När programmet fungerar som det är tänkt, ändra tal.txt, både med värden och framförallt med antalet värden. Lägg särskilt märke till hur medianvärdet förändras när listan har ett jämnt respektive udda antal tal.

Fördjupning: Python har redan en median-funktion [klicka för att visa]

I Python finns redan många färdiga funktioner för statistik. En sådan är median() i modulen statistics.

import statistics

tal = [12, 7, 19, 3, 15]

print(statistics.median(tal))

Det kan vara intressant att se hur sådana funktioner är implementerade och var de finns på datorn.

import statistics

print(statistics.__file__)

Detta skriver ut sökvägen till Python-filen där modulen statistics finns. Den filen kan du öppna och läsa.

Alla funktioner i Python är dock inte skrivna i vanlig Pythonkod. Vissa är inbyggda direkt i Python-motorn.

print(type(sum))

Detta ger:

<class 'builtin_function_or_method'>

Det betyder att sum() är en inbyggd funktion. Den är alltså inte skriven i en vanlig .py-fil utan direkt i Python.

Du kan dock fortfarande läsa dokumentationen direkt i Python:

help(sum)
help(statistics.median)

Detta är ett enkelt sätt att ta reda på hur en funktion fungerar utan att behöva lämna Python.

Uppgift: m05e01

Om du nu känner att du vill träna lite mer på att lösa olika problem där det ingår att läsa in data från fil så finns några uppgifter från en tidigare kodkalender.

5.2.2 Skriva till fil

I avsnitt 5.2.1 läste vi data från en fil och lagrade den i en lista. Nu gör vi tvärtom: vi ska skriva data från programmet till en fil.

När vi skriver till en fil använder vi också open(), men nu anger vi dessutom vilket läge filen ska öppnas i.

För att skriva till en fil använder vi oftast läget "w".

with open("hej.txt", "w") as fil:
    fil.write("Hej!")

Om filen inte finns skapas den. Om filen redan finns skrivs innehållet över.

hej.txt

Hej!

När vi skriver flera rader till en fil måste vi själva lägga till radbrytningen \n.

with open("namn.txt", "w") as fil:
    fil.write("Anna\n")
    fil.write("Erik\n")
    fil.write("Sara\n")

namn.txt

Anna
Erik
Sara

Standardläget är "r"

När vi läser från en fil använder Python normalt läget "r" (read).

Detta är faktiskt standardläget, vilket betyder att det inte behöver skrivas ut.

with open("tal.txt") as fil:
    ...

with open("tal.txt", "r") as fil:
    ...

I kursen kommer vi oftast använda den kortare varianten.

Detta fungerar, men om vi redan har datan i en lista är det oftast bättre att loopa igenom listan.

namn_lista = ["Anna", "Erik", "Sara"]

with open("namn.txt", "w") as fil:
    for namn in namn_lista:
        fil.write(namn + "\n")

Här används samma idé som när vi läste från fil:

  • i 5.2.1: fil → lista
  • nu i 5.2.2: lista → fil

Vanligt fel

Funktionen write() kan bara skriva strängar. Om du försöker skriva ett heltal direkt får du ett fel.

with open("tal.txt", "w") as fil:
    fil.write(12)
with open("tal.txt", "w") as fil:
    fil.write(str(12))

Om värdet inte redan är en sträng behöver du alltså konvertera det med str().

Skillnaden mellan "w" och "a"

Det finns två vanliga lägen när man skriver till fil:

  • "w" = write → skriv över hela filen
  • "a" = append → lägg till i slutet av filen

I detta moment kommer vi oftast använda "w", eftersom det är tydligt och lätt att förstå.

with open("logg.txt", "a") as fil:
    fil.write("Ny rad\n")

Om filen redan innehåller data läggs den nya raden till längst ner i filen.

logg.txt

Start
Första körningen
Ny rad

Övning 5.2c – skriv namn till fil

Skapa en lista med minst tre namn.

Skriv sedan ett program som sparar alla namn i en fil som heter klasslista.txt, ett namn per rad.

Lösningsförslag

klasslista = ["Alva", "Noah", "Maja"]

with open("klasslista.txt", "w") as fil:
    for namn in klasslista:
        fil.write(namn + "\n")

Övning 5.2d – skriv tal till fil

Skapa en lista med minst fyra heltal.

Skriv sedan ett program som sparar talen i en fil som heter temperaturer.txt, ett tal per rad.

Tänk på att talen först måste konverteras till strängar.

Lösningsförslag

temperaturer = [18, 21, 16, 23]

with open("temperaturer.txt", "w") as fil:
    for tal in temperaturer:
        fil.write(str(tal) + "\n")

Övning 5.2e – loggfil

Skriv ett program som låter användaren skriva loggmeddelanden.

Varje meddelande ska sparas i filen logg.txt.

När användaren trycker Enter utan att skriva något ska programmet avslutas.

Tips: använd läget "a" så att nya meddelanden läggs till i slutet av filen.

Exempel på körning

Loggmeddelande: Programmet startade
Loggmeddelande: Testkörning
Loggmeddelande: Allt fungerar
Loggmeddelande:

logg.txt efter körningen

Programmet startade
Testkörning
Allt fungerar

Lösningsförslag

while True:
    meddelande = input("Loggmeddelande: ")

    if meddelande == "":
        break

    with open("logg.txt", "a") as fil:
        fil.write(meddelande + "\n")

Uppgift: m05u06 – skapa en rapport

Du ska skriva ett program som samlar in tal från användaren och skapar en rapport i en fil.

Del 1 – samla in tal

  • Fråga användaren efter tal.
  • När användaren skriver stop avslutas inmatningen.
  • Alla tal ska sparas i filen tal.txt.
  • När programmet startar ska filen tal.txt skapas eller tömmas.
  • Därefter ska varje nytt tal läggas till i filen.
  • Du behöver därför använda både "w" och "a".

Del 2 – läs in talen

  • Läs alla tal från tal.txt.
  • Konvertera dem till heltal.
  • Lagra dem i en lista.

Del 3 – skapa en rapport

Skapa en ny fil som heter rapport.txt.

I rapporten ska följande information skrivas ut på ett tydligt sätt:

  • alla tal
  • antal tal
  • summan av talen
  • största och minsta talet
  • medelvärdet
  • medianvärdet
  • hur många tal som är större än 10
  • talen i sorterad ordning

All information ska skrivas till rapport.txt.

Det enda som ska skrivas ut i konsollen är:

Rapporten genererad!

rapport.txt

RAPPORT
=======

Tal: [12, 7, 19, 3, 15]
Antal tal: 5
Summa: 56
Största tal: 19
Minsta tal: 3
Medelvärde: 11.2
Medianvärde: 12
Antal tal större än 10: 3
Sorterad lista: [3, 7, 12, 15, 19]

5.2.3 Strukturerad data i filer

I tidigare avsnitt har vi arbetat med filer där en rad innehåller ett värde.

Exempel på enkel datafil

12
8
15
9

Men många filer innehåller flera värden på samma rad.

Exempel på strukturerad data (resultat.txt)

Anna;12;14;16
Erik;8;10;9
Sara;15;13;17

Här representerar varje rad:

  • ett namn
  • tre delresultat

För att arbeta med denna typ av data behöver vi dela upp raden i flera delar.

Det görs med metoden:

split()

Den delar upp en sträng baserat på ett visst tecken, i detta fall ;.

5.2.3.1 Dela upp en rad med split()

Vi börjar med att läsa en rad från filen och dela upp den.

with open("resultat.txt") as fil:
    for rad in fil:
        delar = rad.strip().split(";")
        print(delar)

Utskrift

['Anna', '12', '14', '16']
['Erik', '8', '10', '9']
['Sara', '15', '13', '17']

Resultatet blir en lista där varje del av raden är ett element.

Viktigt

All data som läses från en fil är text.

Även om något ser ut som ett tal:

12

så är det egentligen:

"12"

Om vi vill räkna med värdet måste det konverteras:

int("12")

5.2.3.2 Lagra strukturerad data

Nu ska vi lagra informationen i en struktur som är lätt att arbeta vidare med.

Vi kan till exempel i en lista lagra varje elev så här:

["Anna", [12, 14, 16]]

Det innebär:

  • första värdet = namn
  • andra värdet = lista med resultat

Hela filen kan då representeras så här:

[
    ["Anna", [12, 14, 16]],
    ["Erik", [8, 10, 9]],
    ["Sara", [15, 13, 17]]
]

Följande program läser filen och bygger denna struktur.

elever = []

with open("resultat.txt") as fil:
    for rad in fil:
        delar = rad.strip().split(";")
        namn = delar[0]
        poang = []
        for varde in delar[1:]:       # [1:] - starta loopen på listans index 1
            poang.append(int(varde))
        elever.append([namn, poang])

print(elever)

5.2.3.3 Skriva tillbaka strukturerad data

När vi skriver tillbaka datan måste vi skapa en rad som ser ut som i filen:

Anna;12;14;16

Det gör vi genom att bygga en textsträng för varje elev.

with open("resultat_ny.txt", "w") as fil:
    for elev in elever:
        namn = elev[0]
        poang = elev[1]
        text_poang = []
        for p in poang:
            text_poang.append(str(p))
        rad = namn + ";" + ";".join(text_poang)
        fil.write(rad + "\n")

Här används "w" eftersom vi skriver om hela filen.

5.2.3.4 Strukturerad data med dict

Vi kan också lagra samma data med en dict istället för en nästlad lista.

Då kan varje namn fungera som nyckel och listan med resultat som värde.

En möjlig struktur blir då:

{
    "Anna": [12, 14, 16],
    "Erik": [8, 10, 9],
    "Sara": [15, 13, 17]
}

Detta kan vara praktiskt om vi snabbt vill slå upp en viss elev med hjälp av namnet.

Följande program läser in filen resultat.txt och lagrar datan i en dictionary:

elever = {}

with open("resultat.txt") as fil:
    for rad in fil:
        delar = rad.strip().split(";")
        namn = delar[0]

        poang = []
        for varde in delar[1:]:
            poang.append(int(varde))

        elever[namn] = poang

print(elever)       # Utskrift: {'Anna': [12, 14, 16], 'Erik': [8, 10, 9], 'Sara': [15, 13, 17]}

Om vi vill skriva tillbaka datan till en ny fil behöver vi loopa över både nycklar och värden.

with open("resultat_ny.txt", "w") as fil:
    for namn, poang in elever.items():

        text_poang = []
        for p in poang:
            text_poang.append(str(p))

        rad = namn + ";" + ";".join(text_poang)
        fil.write(rad + "\n")

Resultatet i resultat_ny.txt blir samma typ av fil som tidigare:

resultat_ny.txt

Anna;12;14;16
Erik;8;10;9
Sara;15;13;17

Övning 5.2f – undersök datafilen

Skapa filen temperaturer.txt med innehållet:

Alingsås;12;14;11;9;4
Göteborg;15;16;14
Stockholm;10;8;6;5

Skriv ett program som:

  1. läser filen
  2. delar upp varje rad med split(";")
  3. skriver ut stadens namn
  4. skriver ut hur många mätpunkter som finns

Exempel på utskrift

Alingsås har 5 mätpunkter
Göteborg har 3 mätpunkter
Stockholm har 4 mätpunkter

Lösningsförslag

with open("temperaturer.txt") as fil:

    for rad in fil:

        delar = rad.strip().split(";")

        stad = delar[0]

        antal = len(delar) - 1

        print(f"{stad} har {antal} mätpunkter")

Övning 5.2g – beräkna med temperaturer

Utgå från filen temperaturer.txt med innehållet:

temperaturer.txt

Alingsås;12;14;11;9;4
Göteborg;15;16;14
Stockholm;10;8;6;5

Skriv ett program som:

  1. läser filen
  2. delar upp varje rad med split(";")
  3. konverterar temperaturerna till heltal
  4. beräknar medeltemperaturen för varje stad
  5. skriver ut resultatet

Exempel på utskrift:

Utskrift

Alingsås: 10
Göteborg: 15
Stockholm: 7

Lösningsförslag

with open("temperaturer.txt") as fil:
    for rad in fil:
        delar = rad.strip().split(";")

        stad = delar[0]
        temperaturer = []

        for varde in delar[1:]:
            temperaturer.append(int(varde))

        medel = sum(temperaturer) / len(temperaturer)

        print(f"{stad}: {medel:.2f}")

Uppgift: m05u07

Du ska skapa en rapport baserad på temperaturdata.

Filen temperaturer.txt innehåller:

Alingsås;12;14;11;9;4
Göteborg;15;16;14
Stockholm;10;8;6;5

Programmet ska:

  1. läsa filen
  2. beräkna medeltemperatur
  3. beräkna median (använd din funktion från tidigare moment)
  4. skriva resultatet till filen rapport.txt

Rapportfilen ska se ut så här:

Alingsås - medel: 10, median: 11
Göteborg - medel: 15, median: 15
Stockholm - medel: 7, median: 7

Det enda som får skrivas ut i terminalen är:

Rapport skapad!

Tips:

  • importera din funktionsfil
  • medianfunktionen ska ta en lista som parameter
  • använd "w" när du skriver rapporten

5.3 JSON – strukturerad data på riktigt

Hittills i momentet har vi arbetat med textfiler där vi själva bestämt hur datan ska se ut. Vi har till exempel använt filer med ett värde per rad och filer där flera värden skiljs åt med semikolon.

Det fungerar bra, men det kräver också att vi själva delar upp texten med split(), konverterar värden och bygger upp strukturen i programmet.

JSON är ett vanligt format för att lagra strukturerad data. Det används i många riktiga program och på webben när system skickar information till varandra.

Detta avsnitt är svårare

Detta avsnitt bygger på att du redan känner dig trygg med:

  • att läsa och skriva enklare textfiler
  • listor
  • dictionaries

Om du fortfarande är osäker på filhanteringen i 5.2 eller på dictionaries från tidigare moment bör du repetera det först.

Fördelen med JSON är att vi kan lagra data i en struktur som redan liknar Pythons listor och dictionaries.

5.3.1 Vad är JSON?

JSON står för JavaScript Object Notation. Trots namnet används formatet i många olika programmeringsspråk, även i Python.

En JSON-fil kan till exempel se ut så här:

temperaturer.json

{
    "stad": "Alingsås",
    "temperaturer": [12, 14, 11, 9, 4]
}

I Python motsvaras detta av en dictionary:

data = {
    "stad": "Alingsås",
    "temperaturer": [12, 14, 11, 9, 4]
}

Det betyder att JSON passar bra när vi vill lagra data som har:

  • nycklar, till exempel "stad"
  • värden, till exempel "Alingsås"
  • listor, till exempel flera temperaturer

Viktigt

JSON är inte samma sak som en Python-dictionary, men strukturen är väldigt lik. Därför är det lätt att arbeta med JSON i Python om man redan kan dictionaries.

5.3.2 Läsa JSON från fil

För att läsa JSON använder vi modulen json.

När vi läser in en JSON-fil med json.load() omvandlas datan till vanliga Python-strukturer, oftast listor och dictionaries.

import json

with open("temperaturer.json", encoding="utf-8") as fil:
    data = json.load(fil)

print(data)         # Utskrift: {'stad': 'Alingsås', 'temperaturer': [12, 14, 11, 9, 4]}

Om filen innehåller JSON-exemplet från tidigare avsnitt blir data en dictionary.

print(data["stad"])
print(data["temperaturer"])

Utskrift

Alingsås
[12, 14, 11, 9, 4]

Vi kan sedan arbeta vidare med datan precis som med vanliga dictionaries och listor.

for temperatur in data["temperaturer"]:
    print(temperatur)

5.3.3 Skriva JSON till fil

För att spara en dictionary eller lista som JSON använder vi json.dump().

import json

data = {
    "stad": "Alingsås",
    "temperaturer": [12, 14, 11, 9, 4]
}

with open("temperaturer.json", "w", encoding="utf-8") as fil:
    json.dump(data, fil, indent=4, ensure_ascii=False)

Utskrift

{
    "stad": "Alingsås",
    "temperaturer": [
        12,
        14,
        11,
        9,
        4
    ]
}

Svenska tecken i JSON

När vi arbetar med JSON finns två saker som påverkar hur svenska tecken som å, ä och ö sparas i filen.

  • encoding="utf-8" - Detta styr hur texten lagras i filen. UTF-8 är en standard som klarar alla vanliga tecken, inklusive svenska bokstäver.
  • ensure_ascii=False - Detta styr hur tecknen skrivs ut i JSON. Om du inte använder denna inställning kan svenska tecken omvandlas till koder, till exempel å → \u00e5.

Tillsammans gör dessa inställningar att JSON-filen blir både korrekt sparad och lätt att läsa för oss människor. Python bryr sig inte om hur filen är formaterad, men för oss människor är det lättare att läsa med indrag och med korrekt utskrivna tecken.

with open("temperaturer.json", "w", encoding="utf-8") as fil:
    json.dump(data, fil, indent=4, ensure_ascii=False)

5.3.4 Flera objekt i samma JSON-fil

Ofta vill vi lagra mer än en sak i samma fil. Då kan vi använda en lista av dictionaries.

temperaturer_flera.json

[
    {
        "stad": "Alingsås",
        "temperaturer": [12, 14, 11, 9, 4]
    },
    {
        "stad": "Göteborg",
        "temperaturer": [15, 16, 14]
    },
    {
        "stad": "Stockholm",
        "temperaturer": [10, 8, 6, 5]
    }
]

När vi läser in denna fil får vi en lista där varje element är en dictionary.

import json

with open("temperaturer_flera.json", encoding="utf-8") as fil:
    data = json.load(fil)

print(type(data))
print(data[0])
print(data[0]["stad"])

Utskrift

<class 'list'>
{'stad': 'Alingsås', 'temperaturer': [12, 14, 11, 9, 4]}
Alingsås

Vi kan nu loopa över hela listan:

for post in data:
    print(post["stad"])

Övning 5.3a – läsa JSON

Skapa filen temperaturer.json med innehållet:

temperaturer.json

{
    "stad": "Alingsås",
    "temperaturer": [12, 14, 11, 9, 4]
}

Skriv ett program som:

  1. läser in filen
  2. skriver ut stadens namn
  3. skriver ut hur många temperaturvärden som finns

Lösningsförslag

import json

with open("temperaturer.json", encoding="utf-8") as fil:
    data = json.load(fil)

print(data["stad"])
print(len(data["temperaturer"]))

Övning 5.3b – beräkna med JSON-data

Skapa filen temperaturer_flera.json med innehållet:

temperaturer_flera.json

[
    {
        "stad": "Alingsås",
        "temperaturer": [12, 14, 11, 9, 4]
    },
    {
        "stad": "Göteborg",
        "temperaturer": [15, 16, 14]
    },
    {
        "stad": "Stockholm",
        "temperaturer": [10, 8, 6, 5]
    }
]

Skriv ett program som:

  1. läser in filen
  2. loopar över alla städer
  3. beräknar medeltemperaturen för varje stad
  4. skriver ut resultatet

Exempel på utskrift

Alingsås: 10
Göteborg: 15
Stockholm: 7

Lösningsförslag

import json

with open("temperaturer_flera.json", encoding="utf-8") as fil:
    data = json.load(fil)

for post in data:
    stad = post["stad"]
    temperaturer = post["temperaturer"]
    medel = sum(temperaturer) / len(temperaturer)
    print(f"{stad}: {medel:.2f}")

Uppgift: m05u08

Du ska skapa en rapport baserad på JSON-data.

Filen temperaturer_flera.json innehåller flera städer och temperaturer.

Programmet ska:

  1. läsa in JSON-filen
  2. beräkna medeltemperatur för varje stad
  3. beräkna median med hjälp av din medianfunktion från tidigare
  4. skapa en ny JSON-fil med resultatet

Resultatfilen kan till exempel se ut så här:

rapport.json

[
    {
        "stad": "Alingsås",
        "medel": 10,
        "median": 11
    },
    {
        "stad": "Göteborg",
        "medel": 15,
        "median": 15
    },
    {
        "stad": "Stockholm",
        "medel": 7,
        "median": 7
    }
]

Tips:

  • använd json.load() för att läsa in
  • använd json.dump() för att skriva ut
  • använd indent=4 så att filen blir lättläst
  • importera din medianfunktion från tidigare fil om du vill

5.4 Avrundning av momentet

Detta moment avslutas med två diagnoser som säkerställer att du har förstått grunderna:

  • En teoretisk diagnos i form av en quiz med 10 frågor på de viktigaste begreppen.
  • En praktisk diagnos där du får skriva kod och lösa en eller flera uppgifter med fokus på det du lärt dig i momentet.

Syftet är att både kontrollera dina kunskaper och ge dig en chans att öva på provliknande situationer.

5.4.1 Checklista inför diagnosen

För att vara förberedd bör du kunna:

Checklistan