5. Moment05 - Funktioner och datalagring

Funktioner och datalagring är bra redskap för att skapa en bättre och tydligare struktur i applikationer. Funktioner används för att samla exempelvis beräkningar som ska göras flera gånger i koden under ett funktionsnamn. Det är också möjligt att skapa separata .py filer där alla funktioner som används i en kod samlas.

I avsnittet om datalagring kommer vi titta lite på hur vi med hjälp av Python kan läsa, manipulera och skriva data till textfiler (.txt). Detta kommer vara ett ytterligare steg i vår resa att arbeta med applikationer som består av flera olika filer som påverkar varandra.

Som avslutning på detta moment så skall du få bygga en större applikation då du skall bygga en liten bankapplikation.

Info

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

5.1 Funktioner

Genomgång [klicka för att visa]

Vi har tidigare tittat på några inbyggda funktioner och det finns väldigt många sådana men vi kan också skapa egna funktioner.

En funktion är en del kod som utför någon beräkning, utskrift eller något liknande som vi behöver i vår kod. En funktion är perfekt att använda till saker som skall göras flera gånger, behöver vi ändra något så ändras det bara på ett ställe och slår igenom i hela applikationen. I vissa programmeringsspråk pratar vi om metoder men det är samma sak som en funktion.

Grundtanken är att en funktion skall göra en sak, behöver vi utföra två beräkningar är det ofta bättre att ha en funktion till varje beräkning.

Varför vill vi ha två funktioner? Tänk dig att vi skall skapa funktioner för att beräkna area och omkrets på en cirkel. I detta exempel kan vi skapa en gemensam funktion som räknar ut båda sakerna samtidigt och skriver ut resultatet, men sedan vill vi bara beräkna arean i en annan uppgift och har vi då en sammansatt funktion så kan vi inte använda den utan måste skapa en ny. Av denna anledning så är det enklare att skapa två funktioner som vi anropar.

5.1.1 Funktionsanrop

Genomgång [klicka för att visa]

Låt oss återvända till grunderna och den klassiska applikationen Hello World och bygga om applikationen så att det innehåller en egen funktion, hello_world().

Kodexempel: Hello World-funktionen

def hello_world():
  print('Hello, World!')

För att definiera en ny funktion börjar vi med att skriva def, följt av funktionens namn. Lägg märke till att likt loopar måste vi ha ett : i slutet på raden med funktionsnamnet och allt som ska tillhöra funktionen måste indenteras.

Skulle vi köra applikationen som det ser ut nu kommer ingenting att hända. Detta beror på att funktionen endast är definierad, men aldrig anropas.

Kodexempel: Hello World-funktionen med funktionsanrop

def hello_world():
  print('Hello, World!')

hello_world()

Utskrift

Hello, World!

Var noga med att inte anropa funktioner innan de definierats, nedanstående exempel producerar en error:

Kodexempel: Hello World-funktionen med funktionsanrop

hello_world()

def hello_world():
  print('Hello, World!')

Error

NameError: name 'hello_world' is not defined

Uppgift: m05u01

Skapa en funktion som skriver ditt namn, din klass och din skola. Anropa sedan funktionen.

5.1.2 Inparametrar

Genomgång [klicka för att visa]

För att kunna skapa funktioner som kan göra lite mer än att bara skriva ut samma sak varje gång så kommer vi behöva skicka in något i funktionen som den kan jobba med. Det som skickas in till en funktion kallas inparameter. Inparametrar kan vara av vilken datatyp som helst, så det är viktigt att du kommenterar vad dina funktioner gör och vilken datatyp funktionen förväntar sig.

Ska vi i en applikation utföra addition av två tal och skriva ut svaret upprepade gånger kan det vara fördelaktigt att skapa en funktion som beräknar summan.

Kodexempel: En enkel summafunktion

def summa(a, b):
  c = a + b
  print('{} + {} = {}'.format(a, b, c))

summa(1, 3)
summa(1.5, 3.14)

a = 2
b = -5
summa(a, b)

summa(4567, 8901)

x = 2
y = 13
summa(x, y)

Utskrift

1 + 3 = 4
1.5 + 3.14 = 4.640000000000001
2 + -5 = -3
4567 + 8901 = 13468
2 + 13 = 15

5.1.2.1 lokala vs globala variabler

I exemplet ovan så är variablerna a och b inne i funktionen summa(a, b) lokala. Med detta menas att a och b endast existerar inuti funktionen och om vi har skapat variablerna a och b utanför funktionen är dessa globala variabler. De lokala och globala variablerna påverkas inte av varandra så länge vi inte gör ett aktivt val.

Detta kan vara lurigt att skapa två olika variabler som har samma namn om de skall representera olika saker. I exemplet ovan hade det varit bättre att döpt om inparametrarna till något annat för att slippa risken att det blir något fel. Men ibland kan det finnas läge då vi vill kunna påverka en global variabels värde inne i en funktion. Tänk exempelvis att vi har byggt en applikation för att hålla koll på ett bankkontos saldo. I denna applikation så har vi en global variabel saldo som vi vill kunna uppdatera inne i funktionerna deposit() och withdraw(). Då kan det vara smidigt att både kunna komma åt saldot och även uppdatera det inifrån funktionerna.

Här kommer ett exempel på hur det fungerar med inparametrar respektive globala variabler.

Kodexempel: Global variabel vs inparameter

def change_tal_parameter(tal):
    tal += 1
    print(f"Talets värde inne i funktionen: {tal}")


def change_tal_global():
    global tal
    tal += 1
    print(f"Talets värde inne i funktionen: {tal}")


tal = int(input("Mata in ett heltal: "))
print(f"Det inmatade talet är: {tal}")
change_tal_parameter(tal)
print(f"Talets värde efter funktionen med inparameter: {tal}")
change_tal_global()
print(f"Talets värde efter funktionen som deklarerar variabeln som global: {tal}")

Utskrift

Mata in ett heltal: 5
Det inmatade talet är: 5
Talets värde inne i funktionen: 6
Talets värde efter funktionen med inparameter: 5
Talets värde inne i funktionen: 6
Talets värde efter funktionen som deklarerar variabeln som global: 6

Att använda globala variabler har sina fördelar men det är viktigt att du förstår vad det innebär. Det är bra i vissa lägen men inte i alla.

Uppgift: m05u02

I inlämningsuppgiften till moment02 så byggde du en applikation där användaren fick ange radien till en cirkel och sedan så beräknade du area och omkrets som sedan skrevs ut. Bygg nu om denna så att utskrifter för area och omkrets utförs i två olika funktioner.

5.1.3 Returvärde

Genomgång [klicka för att visa]

I funktionerna summa och rektangel_area är det främsta syftet kanske inte att skriva ut någon snygg formaterad text, utan snarare att utföra någon beräkning.

Vill vi bara få ut det beräknade värdet i en funktion kan vi använda return.

Beräkna area av rektangel

a = 3
b = 4
c = 5
d = 6

def rektangel_area(sida1, sida2):
  area = sida1 * sida2
  return area

print(f"En rektangel med sidorna {a} och {b} har arean {rektangel_area(a, b)}")
print(f"och en rektangel med sidorna {c} och {d} har arean {rektangel_area(c, d)}")

rektanglar_summa = rektangel_area(a, b) + rektangel_area(c, d)
print(f"Summan av rektanglarnas areor är {rektanglar_summa}")

Utskrift

En rektangel med sidorna 3 och 4 har arean 12
och en rektangel med sidorna 5 och 6 har arean 30
Summan av rektanglarnas areor är 42

I exemplet ovan så kan du se hur jag låter funktionsanropet, rektangel_area(a, b) fungera som en variabel vid utskrift på rad 10. funktionsanropet kommer i detta fallet behandas som svaret från funktionen vilket är ett tal.

Uppgift: m05u03

Arbeta om funktionerna i uppgift m05u02 så att det endast är värdet på cirkelns area och omkrets som returneras från funktionerna.

Uppgift: m05e01 (extra) (omdöpt från m05u04)

Arbeta om inlämningsuppgift m03 (den svåraste nivån du gjorde) och skapa funktioner som utför de olika skatteberäkningarna i uppgiften.

5.1.4 Funktioner i separat fil

Genomgång [klicka för att visa]

Vi ska nu ta första klivet från att endast jobba i en fil per applikation och skapa en separat fil, functions.py, där vi lägger in våra funktioner.

Detta är ett smidigt sätt att snygga till våra filer och förhoppningsvis öka läsbarheten.

Kodexempel: functions.py

def hello_world():
  print('Hallå, världen!')

def summa(tal1, tal2):
  return tal1 + tal2

def division(täljare, nämnare):
  return täljare / nämnare

def rektangel_area(sida1, sida2):
  if sida1 == sida2:
    area = sida1**2
    return area
  else:
    area = sida1 * sida2
    return area

Det viktiga att tänka på är att alla filer som tillhör samma projekt eller applikation läggs i samma mapp. I det här fallet ligger filen functions.py i samma mapp som test.py.

Här följer tre olika sätt att använda functions.py från test.py.

Kodexempel: Variant 1 av test.py

# För att komma åt funktioner från functions.py måste vi
# skriva functions.funktionens_namn()
import functions

functions.hello_world()

Kodexempel: Variant 2 av test.py

# Importerar alla funktioner från functions.py och vi
# behöver inte använda functions.funktionens_namn()
from functions import *

hello_world()

Kodexempel: Variant 3 av test.py

# Importerar endast funktionen hello_world och ger den
# Importerar funktionerna hello_world och rektangel_area
# och ger funktionerna nya anropsnamn
from functions import hello_world as hello
from functions import rektangel_area as rekt

hello()
print(rekt(5, 3))

Uppgift: m05u05

Flytta ut alla dina funktioner från m05u03 i en separat fil. Importera sedan funktionerna på valfritt sätt.

Tidigare filmad genomgång av funktioner [klicka för att visa]

Här är en film som jag tidigare har spelat in som går igenom alla delar så här långt i momentet. Fördelen med denna film är att jag i en enda film går igenom de olika delarna som du som elev behöver kunna.

En liten varning för ljudet är på sin plats, det är lite ojämnt och det låter som att jag ibland använder en kofot för att skriva på tangentbordet.

5.1.5 Dokumentation av funktioner

Genomgång [klicka för att visa]

När vi skriver egna funktioner börjar dokumentationen bli allt viktigare, särskilt när du senare i kursen kommer arbeta på projekt som sträcker sig över längre tid.

Det kan vara bra att skaffa sig en god vana i att dokumentera sina funktioner tydligt men kortfattat.

5.1.5.1 Enkelt sätt att dokumentera funktioner

Innan vi går in på pythonspecifika sätt att kommentera och dokumentera funktioner så kan det vara bra att se på ett allmänt exempel. Detta sätt att kommentera fungerar alltid.

Kodexempel: Enkel dokumentation av funktioner

# area_kvadrat
# funktionen beräknar arean av en kvadrat
# inparameter: sida, kvadratens sida
# returvärde: kvadratens area
def area_kvadrat(sida):
  area = sida ** 2
  return area

Det viktigaste är att ni kommenterar koden och funktionerna, sedan vilken teknik ni använder er av har underordnad betydelse. Tänk på att du snart kommer bygga större projekt med många funktioner som ligger i en egen fil, då är det viktigt att kommentera/dokumentera dessa funktioner på ett bra sätt.

I en enklare och kortare funktion så räcker det ofta med denna typ av dokumentation. Givetvis är det alltid bra att komplettera med kommentarer för koden också.

5.1.5.2 Pythons egen struktur

I python finns tydliga rekommendationer kring hur saker dokumenteras, bland annat funktioner. Detta är inget unikt med Python utan de flesta programmeringsspråk har sin standard. Ofta vill man använda en standard för att sedan kunna generera dokumentationer av dessa kommentarer. Nedan följer några exempel samt en checklista att följa när du dokumenterar dina funktioner.

Kodexempel: Funktionsdokumentation flera rader

def division(täljare, nämnare):
  """Beräknar kvoten mellan två tal.
 
  Nämnaren får ej vara 0.
  """

  return täljare / nämnare

I exemplet ser vi att på första raden ges en kort förklaring på vad funktionen gör. Nästa rad lämnas tom för att markera att sammanfattningen är slut och på den tredje raden står det en kort information om att nämnaren ej får anges som 0.

Märk att i sammanfattningen ska inte funktionens namn anges. Sammanfattningen ska dessutom inte vara för teknisk.

Kodexempel: Fler dokumentationsexempel

def hello_world():
  """Skriver ut hallå världen."""

  print('Hallå, världen!')

def summa(tal1, tal2):
  """Beräknar summan av två tal."""

  return tal1 + tal2

def rektangel_area(sida1, sida2):
  """Beräknar arean av en rektangel."""

  if sida1 == sida2:
    area = sida1**2
    return area
  else:
    area = sida1 * sida2
    return area
Checklista funktionsdokumentation
  • Bygg upp dokumentationen i en sträng med 3 dubbelfnuttar.
  • Den första raden är en sammanfattning / kort förklaring av funktionem, inled med stor bokstav, avsluta med punkt.
  • Den andra raden ska vara tom.
  • Från rad tre kan du börja skriva detaljer om hur funktionen fungerar, dess begränsningar osv.

Mer läsning för den intresserade.

5.1.5.3 Pythons dokumentation i PyCharm

Om du använder pycharm och börjar skriva tre citationstecken och trycker på enter så kommer du få fram hela funktionens struktur inom kommentaren. Då kan det se ut på följande sätt;

Kodexempel: pycharms dokumentationshjälp

def area_kvadrat(sida):
  """ Funktion som beräknar arean på en kvadrat.
 
  :param sida: kvadratens sida
  :return: kvadratens area
  """
  area = sida ** 2
  return area

Där :param sida: står för den inparameter som finns till funktionen och :return: står för vad som skall returneras från funktionen.

Uppgift: m05u06

Fortsätt på m05u05 och kommentera dina funktioner enligt någon av exemplen för att kommentera funktioner.

Uppgift: m05u07 - julvarianten

Filen m05u07.py får bara innehålla följande:

Filen m05u07.py

from m05u07_funktioner import *
 
julgran(10)
tomten()

Din uppgift är att skapa och arbeta med filen m05u07_funktioner.py så att m05u07.py producerar följande utskrift:

Kodexempel: Utskriften

*
**
***
****
*****
******
*******
********
*********
**********

Ho, Ho, Ho!
Jullovet är nära

Hjälp [klicka för att visa]

Om man vill skriva ut flera tecken, eller en sträng, som är lika så går det också att skriva ut på följande sätt;

Kodexempel: multiplicerad utskrift

print("text"*3)
print("X"*5)

Utskrift

texttexttext
XXXXX

Läge att träna på aktivitetsdiagram och pseudokod om du inte har lyckats med detta tidigare?

Tillägg (svårare) [klicka för att visa]

I grunduppgiften är granen du producerar inte så snygg, den är ju bara halv och saknar en fot.
Redigera funktionen julgran så en ordentlig julgran ritas ut.

Utskrift

         *
        ***
       *****
      *******
     *********
    ***********
   *************
  ***************
 *****************
*******************
        ***
        ***

Ho, Ho, Ho!
Jullovet är nära

Även denna julgran går ju att bygga ut, så här kan den se ut när jag har slumpat fram röda julgranskulor och en gul stjärna i toppen på min gröna gran.

Julgran

Uppgift: m05u07 - den andra varianten

Filen m05u07.py får bara innehålla följande:

Filen m05u07.py

from m05u07_funktioner import *
 
area_rektangel(4, 6)

Din uppgift är att skapa och arbeta med filen m05u07_funktioner.py så att m05u07.py producerar följande utskrift:

Kodexempel: Utskriften

######
######
######
######
Rektangelns area är 24.

Hjälp [klicka för att visa]

Om man vill skriva ut flera tecken, eller en sträng, som är lika så går det också att skriva ut på följande sätt;

Kodexempel: multiplicerad utskrift

print("text"*3)
print("X"*5)

Utskrift

texttexttext
XXXXX

Läge att träna på aktivitetsdiagram och pseudokod om du inte har lyckats med detta tidigare?

Tillägg (svårare) [klicka för att visa]

Det går att sätta defaultvärden på en inparameter så att du kan använda en funktion på flera olika sätt. Här kommer ett exempel.

Kodexempel: defaultvärde för en inparameter

def min_funktion(param1, param2="standardvärde"):
  print(f"Param1: {param1}")
  print(f"Param2: {param2}")
    
# Anrop av funktionen med båda parametrarna
min_funktion("värde1", "värde2")
    
# Anrop av funktionen med bara en parameter
min_funktion("värde1")
    

Utskrift

Param1: värde1
Param2: värde2

Param1: värde1
Param2: standardvärde

Bygg nu om filen m05u07_funktioner.py till m05u07b_funktioner.py så att anropen ger rätt utskrifter.

Filen m05u07b.py

from m05u07b_funktioner import *
 
area_rektangel(4)
area_rektangel(3,6)
area_rektangel(2,3,"*")

Utskrift

####
####
####
####
Kvadratens area är 16.
    
######
######
######
Rektangelns area är 18.    

***
***
Rektangelns area är 6.

5.2 Datalagring

Datalagring är bra att ha koll på av flera olika anledningar. Utvecklare kan ibland vilja skicka ut felmeddelande och variabelvärden i en loggfil. En fysiker som genomfört ett experiment för att bestämma hur mycket vi påverkas av gravitationskraften från jorden här i Alingsås har samlat in några hundra olika datapunkter i ett textdokument som nu ska analyseras. Då är det bra att ha koll på hur vi kan underlätta filhantering och arbeta med datalagring i Python.

En fil på en dator är en sammanhängande mängd data som lagras under ett specifikt namn. Vi kan välja att skapa en fil med massa olika ändelser men eftersom vi i början väljer att skapa textfiler så är det lämpligt att ge våra filer ändelsen ".txt".

I denna kurs kommer vi fokusera på enkla filer för datalagring. Men inom datalagring så används en mängd olika tekniker för att lagra och transportera data, du kanske har hört talas om XML, JSON, databaser eller olika API:er. Mer om detta kommer i senare kurser.

När vi läser in data från textfiler är det viktigt att komma ihåg att all data som läses in är av datatypen string om vi inte aktivt gör något åt det. Till exempel genom att utnyttja den enkla konverteringen int() för att konvertera till heltal.

5.2.1 Läsa från fil

Genomgång [klicka för att visa]

Filen vi kommer arbeta med heter datatyper.txt och är en enkel textfil som innehåller fyra rader text. Den här filen kommer vi arbeta med under hela avsnitt 5.2.1

datatyper.txt

string | "@", "Johan", 'Sebastian' | Alla texter och tecken. Markeras med dubbelfnutt(") eller enkelfnutt(')
int | 1, 7, -9 | Står för integer och lagrar heltal
float | 0.0, 4.5, -9.7 | Flyttal, lagrar decimaltal, notera decimalpunkt enligt engelskspråkig standard
boolean | True, False | Boolesk variabel, lagrar sant eller falskt, ofta ett resultat av ett uttryck som (1 < 2)

Innan vi tittar på olika sätt att läsa och skriva ut data från datatyper.txt måste vi öppna filen i vår .py fil med namnet test.py. I exemplet kan du se två alternativ.

Kodexempel: metod 1

# Öppnar filen med open('filnamnet') och sparar i en variabel
f = open('datatyper.txt')

# Här skriver vi kod som läser data från textfilen datatyper.txt

# Stänger filen med .close(). Glömmer du det här riskerar du stöta på allehanda problem med att redigera textfilen.
f.close()

Kodexempel: metod 2

# Med kommandot with håller python filen öppen filen tills det att programmet avslutas
# eller vi hamnar utanför with.
# Använder vi with kan vi alltid vara säkra på att filen stängs korrekt i slutet av programmet,
# även om programmet avslutas oväntat på grund av en error.
with open('datatyper.txt') as f:

  # Här skriver vi kod som läser data från textfilen datatyper.txt.
  # Glöm inte att indentera!

# Skriver vi kod här har python stängt filen (utanför with).

När vi arbetar med filer i Python är det rekommenderat att använda metod 2, främst för att där säkerställs att filen stängs korrekt oavsett vad som händer i applikationen.

5.2.1.1 Dumpad utskrift

Det enklaste sättet att skriva ut data från en fil är helt enkelt att skriva ut allt som filen innehåller på en gång.

Kodexempel: Dumpad utskrift

with open('datatyper.txt') as f:
  print(f.read())

Utskrift

string | "@", "Johan", 'Sebastian' | Alla texter och tecken. Markeras med dubbelfnutt(") eller enkelfnutt(')
int | 1, 7, -9 | Står för integer och lagrar heltal
float | 0.0, 4.5, -9.7 | Flyttal, lagrar decimaltal, notera decimalpunkt enligt engelskspråkig standard
boolean | True, False | Boolesk variabel, lagrar sant eller falskt, ofta ett resultat av ett uttryck som (1 < 2)

5.2.1.2 Läsa radvis från fil

Vill vi ha lite mer kontroll kan vi med hjälp av for-loopar arbeta med textfilen rad för rad.

Kodexempel: Arbeta med textfilen rad för rad

with open ('datatyper.txt') as f:
  for rad in f:
    print (rad, end='')

Utskrift

string | "@", "Johan", 'Sebastian' | Alla texter och tecken. Markeras med dubbelfnutt(") eller enkelfnutt(')
int | 1, 7, -9 | Står för integer och lagrar heltal
float | 0.0, 4.5, -9.7 | Flyttal, lagrar decimaltal, notera decimalpunkt enligt engelskspråkig standard
boolean | True, False | Boolesk variabel, lagrar sant eller falskt, ofta ett resultat av ett uttryck som (1 < 2)

Här står det end='' i utskriften och det beror på att print()-funktionen i normala fall ger en radbrytning. Nu finns det dessutom en radbrytning i filen vilket gör att det skulle bli dubbla radbrytningar.

5.2.1.3 Lagra textfil i en lista

Det är ofta relevant att inte bara läsa från en textfil, utan att lagra i en lista för att senare i programmet kunna arbeta med listan utan att påverka textfilen. Sparar vi data i en lista kan vi dessutom utnyttja listfunktioner som sort() för att snabbt sortera värden i storleksordning.

Kodexempel: Lagra i en lista

datatyp = []

with open('datatyper.txt') as f:
  for rad in f:
    datatyp.append(rad)

# Här har textfilen stängts men eftersom raderna sparats i en lista kan vi komma åt innehållet.
for text in datatyp:
    print(text)

Utskrift

string | "@", "Johan", 'Sebastian' | Alla texter och tecken. Markeras med dubbelfnutt(") eller enkelfnutt(')

int | 1, 7, -9 | Står för integer och lagrar heltal

float | 0.0, 4.5, -9.7 | Flyttal, lagrar decimaltal, notera decimalpunkt enligt engelskspråkig standard

boolean | True, False | Boolesk variabel, lagrar sant eller falskt, ofta ett resultat av ett uttryck som (1 < 2)

5.2.1.4 Lagra textfil i flera listor

I datatyper.txt finns det tydliga tecken på att varje rad innehåller tre olika delar: namnet på en datatyp följt av exempel som följs av en förklaring. Varje del avgränsas med en så kallad delimiter, en avdelare. I datatyper.txt har | använts som avdelare.

Det finns ett värde i att i en och samma textfil använda samma avdelare genom hela filen. Då kan vi nämligen smidigt dela upp de olika delarna i olika listor. Enklaste sättet att dela upp raderna i en textfil efter deras avdelare är att använda .split(avdelare) som delar upp strängen i en lista.

Kodexempel: Lagra i flera listor

datatyp = []
ex_datatyp = []
info_datatyp = []

with open('datatyper.txt') as f:
  for rad in f:
    rad = rad.split('|')

    # Tar bort eventuella inledande "whitespaces" med .lstrip()
    # .rstript() tar bort i slutet och .strip() tar bort både i början och slutet.
    datatyp.append(rad[0].lstrip())
    ex_datatyp.append(rad[1].lstrip())
    info_datatyp.append(rad[2].lstrip())

for i, t in enumerate(datatyp):

  # Vänsterformaterar text och skapar lagom mellanrum med {:<15}
  print('{:<15}{}\n{:<15}{}\n{:<15}{}'.format('Datatyp:', t, 'Exempel:', ex_datatyp[i], 'Förklaring:', info_datatyp[i]))

Utskrift

Datatyp:       string
Exempel:       "@", "Johan", 'Sebastian'
Förklaring:    Alla texter och tecken. Markeras med dubbelfnutt(") eller enkelfnutt(')

Datatyp:       int
Exempel:       1, 7, -9
Förklaring:    Står för integer och lagrar heltal

Datatyp:       float
Exempel:       0.0, 4.5, -9.7
Förklaring:    Flyttal, lagrar decimaltal, notera decimalpunkt enligt engelskspråkig standard

Datatyp:       boolean
Exempel:       True, False
Förklaring:    Boolesk variabel, lagrar sant eller falskt, ofta ett resultat av ett uttryck som (1 < 2)

Uppgift: m05u08

Skapa en fil i ditt projekt som du döper till provpoäng.txt kopiera sedan filens innehåll nedan till den filen du skapat. Filen innehåller 25 tal från 1 till 100 och lägg filen i samma mapp där du arbetar med uppgifter på moment 05. Exempelinnehållet skapas om ifall du laddar om sidan.

  1. Skriv ut talen sorterat från det lägsta till högsta.
  2. Skriv ut hur många tal som finns i listan (utgå ifrån att du inte vet hur många tal provpoäng.txt innehåller).
  3. Skriv ut medelvärdet av talen.
  4. Skriv ut medianvärdet. (Denna är lite svårare och inte obligatorisk)

När du tror att du är klar så testa att ändra data i provpoäng.txt så att du kan få fler eller färre tal att göra dina beräkningar på. Kolla extra noga att du har en hållbar lösning för medianvärdet för jämnt och udda antal resultat.

provpoäng.txt [klicka för att visa]

80
6
24
12
86
7
56
100
98
9
29
7
22
64
99
55
42
7
31
33
6
23
14
4
45

Hjälp [klicka för att visa]

Här kommer lite hjälp att klara av uppgiften (förutom uträkningen av medianvärdet)

  • Läs in filens innehåll till en lista. Fundera på vilken datatyp du vill att datat skall ha? Skriv ut listan så att du ser att den ser ut som du vill att den skall göra.
  • Inbyggda funktioner att använda till en lista hittar du i moment04.

Uppgift: m05u09 (svårare)

Gå in och redigera provpoäng.txt, du ska lägga till ett namn till varje provpoäng enligt formatet:
Sebastian|100 (Avdelaren får du med option+7 i macOS eller alt gr + tangenten till höger om z i Windows.

  1. Skriv ut provpoängen och tillhörande elev sorterat från det lägsta till högsta.
  2. Skriv ut medelvärdet av provpoängen.
  3. Skriv ut eleven som fick det sämsta respektive bästa provresultatet.

provpoäng.txt [klicka för att visa]

Maryam|74
August|19
Maryam|44
Rafael|87
Älva-My|71
Åsa|52
Anna|49
Zeinab|29
Lova|19
Jones|35
Petronella|4
Ivan|100
Clara|97
Åsa|66
Milo|99
August|55
Omar|20
Matteo|69
Ivan|73
Petronella|8
Milo|62
Bertil|41
Iris|27
Folke|28
Cilla|52

Hjälp [klicka för att visa]

Svårigheten här att hålla koll på datat så att en person hela tiden kan kopplas till rätt resultat. Detta blir extra svårt då listan av resultat skall sorteras.

  • Om du skapar två olika listor så behöver resultatlistan sorteras varsamt. Om du kör den inbyggda sorteringsfunktionen så kommer endast denna lista att sorteras. Då tappar du kopplingen till listan med namn. Det är möjligt att skapa en egen sortering som sorterar resultatlistan och samtidigt gör byten av komponenter i namnlistan på samma sätt. Detta ligger dock utanför denna kurs. Men är du intresserad på hur detta kan gå till så sök efter Bubbelsortering/Bubble sort för att se hur man kan skapa en ganska enkel sortering på egen hand.
  • Ett smidigare alternativ är att använda sig av listtypen dictionary. I den listan lagras både värdet och ett index. På det sättet kan vi hålla ihop en persons resultat genom att lagra bägge i samma dictionary. Alla sådana dictionarys kan sedan läggas i en lista så att vi får en samling med alla personers resultat. Sedan finns det möjligheter att sortera denna lista på olika sätt. Se kodexempel nedan.

Kodexempel: Bygga en lista med flera dictionarys

# Importerar pprint för att få en snyggare utskrift
import pprint
# pp innehåller inställningen att indentera utskriften med 2 mellanslag
pp = pprint.PrettyPrinter(indent=2)

# Skapa en lista med 5 familjemedlemmar (dictionarys)
familj = [
    {'namn': 'Anders', 'ålder': 35},
    {'namn': 'Doris', 'ålder': 36},
    {'namn': 'Bodil', 'ålder': 5},
    {'namn': 'Eva', 'ålder': 3},
    {'namn': 'Claes', 'ålder': 3}
]

print("\nSortera listan på ålder, ung till äldre")
pp.pprint(sorted(familj, key=lambda i: i['ålder']))

print("\nSortera listan på ålder, äldre till ung")
pp.pprint(sorted(familj, key=lambda i: i['ålder'], reverse=True))

print("\nSortera listan på namn, a till ö")
pp.pprint(sorted(familj, key=lambda i: i['namn']))

print("\nSortera listan på först ålder, ung till äldre, och sedan på namn, a till ö")
pp.pprint(sorted(familj, key=lambda i: (i['ålder'], i['namn'])))
        

Utskrift

Sortera listan på ålder, ung till äldre
[ {'namn': 'Eva', 'ålder': 3},
  {'namn': 'Claes', 'ålder': 3},
  {'namn': 'Bodil', 'ålder': 5},
  {'namn': 'Anders', 'ålder': 35},
  {'namn': 'Doris', 'ålder': 36}]

Sortera listan på ålder, äldre till ung
[ {'namn': 'Doris', 'ålder': 36},
  {'namn': 'Anders', 'ålder': 35},
  {'namn': 'Bodil', 'ålder': 5},
  {'namn': 'Eva', 'ålder': 3},
  {'namn': 'Claes', 'ålder': 3}]

Sortera listan på namn, a till ö
[ {'namn': 'Anders', 'ålder': 35},
  {'namn': 'Bodil', 'ålder': 5},
  {'namn': 'Claes', 'ålder': 3},
  {'namn': 'Doris', 'ålder': 36},
  {'namn': 'Eva', 'ålder': 3}]

Sortera listan på först ålder, ung till äldre, och sedan på namn, a till ö
[ {'namn': 'Claes', 'ålder': 3},
  {'namn': 'Eva', 'ålder': 3},
  {'namn': 'Bodil', 'ålder': 5},
  {'namn': 'Anders', 'ålder': 35},
  {'namn': 'Doris', 'ålder': 36}]

5.2.2 Skriva till fil

Genomgång [klicka för att visa]

I python kan vi inte bara läsa in data från filer, vi kan också skriva till filer och skapa nya textdokument.

Vi kan i funktionen open() ange vad vi vill kunna göra med filen på följande sätt:

  • open('filnamn') och open('filnamn', 'r') anger båda att filen öppnas i läsläge. I detta läge kan vi endast läsa från filen.
  • open('filnamn', 'w') anger att vi öppnar filen i write, skrivläge. Finns inte filen skapas en ny fil och finns filen redan skriver vi över den.
  • open('filnamn', 'a') anger att vi öppnar i skrivläge utan att skriva över befintlig data. a står för append och allt vi skriver till filen läggs till i slutet.

Kodexempel: Skriva över utskrift.txt

with open('utskrift.txt', 'w') as fw:
  fw.write('1 2 3 4 5\n6 7 8 9 0')

with open('utskrift.txt', 'r') as fr:
  print(fr.read())

Utskrift

1 2 3 4 5
6 7 8 9 0

Pseudokod till kodexempel: Skriva över utskrift.txt

  • Öppna eller skapa utskrift.txt om den inte existerar i skrivläge. Sätt filens storlek till 0 byte.
  • Skriv '1 2 3 4 5\n6 7 8 9 0' till utskrift.txt
  • Spara och stäng utskrift.txt
  • Öppna utskrift.txt i läsläge
  • Skriv ut allt innehåll i utskrift.txt
  • Stäng utskrift.txt

Uppgift: m05u10

Förklara så noggrant du kan vad som händer i följande kod, detta görs lämpligt med kommentarer i koden. Fundera även hur utskrift.txt ser ut om den öppnas i en textredigerare efter koden körts.

Kod till uppgift m05u10

with open('utskrift.txt', 'w') as fw:
  fw.write('''1 2 3
4 5 6
7 8 9
''')
  fw.write('\nHär var det rutigt!')

with open('utskrift.txt', 'r') as fr:
  print(fr.read())

Problem med svenska tecken

Inom programmeringen så kan vi till och från få problem med att svenska tecken som å, ä, ö inte skrivs ut på rätt sätt. Det kan finnas olika anledningar till att det blir på det sättet, ibland är det själva inläsningen från en fil som påverkar hur dessa tecken representeras, då kan det ibland hjälpa att tala om att filen skall läsas in mha teckenkoden UTF-8.

Kodexempel

with open('filen.txt', 'r', encoding='utf-8') as f:
  print(f.read())

I PyCharm kan man också välja vilken teckenkod som en viss fil skall ha, detta ser du längst ner i PyCharm. Om den är satt till UTF-8 så ökar möjligheten att filens innehåll visas korrekt. Slutligen så kan det vara så att filens teckenkod är korrekt, filen läses in på rätt sätt men att genom att du använder någon inbyggd funktion i Python inte kan hantera svenska tecken och då gör om dessa. Då kanske du behöver leta efter en annan funktion, eller så får du helt enkelt hitta en annan lösning på problemet än att använda denna funktion.

Uppgift: m05u11

Den här uppgiften använder samma information som uppgift m04u02.

Ulf har satt in 10 000kr på ett konto med 3 % årlig ränta, hur mycket pengar finns på kontot efter 15 år? Inga fler insättningar görs.
Spara följande information i en textfil för varje år:

  • Det aktuella året (år 1 - 15).
  • Hur mycket pengar som finns på kontot.
  • Den totala procentuella utvecklingen på kontot.

Se till att använda en valfri men tydlig avdelare (delimiter) mellan varje punkt. Avsluta med att skriva ut innehållet i filen på skärmen.

Utskriftsexempel

1|10300|3.00%
2|10609|6.09%
### bortklippta rader ###
14|15126|51.26%
15|15580|55.80%

Uppgift: m05u12

Dags att bygga om/vidare på uppgift m04u08.

Låt användaren mata in heltal ett i taget, 0 avbryter inmatningen av avslutar programmet. Efter varje inmatat heltal så skall det läggas till en rad i en fil där information skall finnas om inmatat tal, högsta och lägsta tal samt summan av alla tal. Fokus på denna uppgift är att lägga till rader i befintlig fil. Använd gärna listor, inmatningskontroller och egenskapade funktioner för uppgiften.
De inbyggda funktionerna sum(), min() och max() får ej användas.

Utskriftsexempel

Inmatat tal: 3, min: 3, max: 3, summa: 3.
Inmatat tal: 7, min: 3, max: 7, summa: 10.
Inmatat tal: 5, min: 3, max: 7, summa: 15.
Inmatat tal: 0, programmet avbryts.
Hela listan: [3, 7, 5], min: 3, max: 7, summa: 15.

Uppgift: m05u13

Bygg vidare på m05u09. När du sorterat poängen, plockat ut medelvärdet (medianvärdet?) och eleverna med sämst och bäst provresultat ska all den här informationen skriva in i en textfil där svaret lagras.

För att kontrollera att allt ser bra ut ska du till slut läsa in denna textfil och skriva ut hela filens innehåll i programmet.

Uppgift: m05e02

Pelle fick i uppdrag av sin lärare att simulera ett antal yatzyslag. Tyvärr har han tappat bort koden som skapade alla slag men han har kvar filen som innehåller en mängd listor där varje lista representerar ett tärningsslag med fem tärningar. Kan du hjälpa Pelle att ta reda på hur många gånger han har slagit yatzy?

Hur många gånger borde Pelle få yatzy på de antalet gånger han har slagit?

Eftersom Pelle har tappat bort filen som han använde för att skapa alla tärningsslagen kanske du kan hjälpa honom även med denna filen? Pelle minns att kravet från läraren var att användaren skulle få frågan hur många yatzyslag man ville slå och sedan skulle det antalet slag slumpas fram.

Uppgift: m05e03

I unga programmerares kodkalender för 2020 så finns det flera roliga uppgifter. I lucka 17 så behöver du använda både funktioner och filer för att lösa uppgiften.

5.2.3 JSON

Svårare avsnitt

Detta avsnitt är lite mer utmanande än det vi gjort tidigare i detta moment. Det är dock tekniker som när ni väl har lärt er den kommer underlätta avsevärt för uppgifter som kommer göras för att visa på kunskaper för högre betyg på kursen.

Att lagra data i fil är smidigt om det är enklare data som skall lagras, men när vi behöver lagra mer komplex data så behöver vi kraftfullare tekniker till vår hjälp. Vi skall börja med att kika på ett format som heter JSON (JavaScript Object Notation) är en lagringsstruktur som används för att lagra och utbyta data mellan olika system. En stor fördel med JSON är att det är relativt lätt för våra ögon att läsa innehållet i filen. JSON har också en struktur som passar väldigt bra ihop med dictionarys då uppbyggnaden liknar varandra.

Enkelt JSON-format

{
  "Name":"Anders",
  "Age":17
}

Här har vi info om en person, Anders som är 17 år gammal. Som du ser så är själva sättet att skriva denna informationen helt identisk med det sättet som vi använder ett dictionary på.

Om vi nu skulle vilja spara information om flera personer så är det ju lämpligt att lagra flera dictionarys i en lista. Skulle vi skriva detta i JSON så ser det ut på följande sätt;

[
  {
     "Name":"Anders",
     "Age":17
  },
  {
     "Name":"Bodil",
     "Age":18
  },
  {
     "Name":"Cilla",
     "Age":19
  },
  {
     "Name":"David",
     "Age":20
  }
]

Ok, då vet vi att JSON liknar de strukturer som vi redan kan använda i Python med listor och dictionarys. Då skall vi kika lite på några exempel på hur vi kan använda JSON för att lagra info till en fil och även läsa in från filen. Jag gör detta i några olika steg. Håll koll på vad som är JSON-filer (filändelse .json), vad som är pythonkod och hur utskrifterna ser ut.

5.2.3.1 Läsa från fil

Vi börjar med att läsa in från fil, då har vi först en fil som jag har döpt till teacher.json som innehåller information om en lärare. Filändelsen här är .json men det skulle också kunna vara .txt, eller något helt annat men det är lämpligt att döpa filerna efter innehåll.

teacher.json

{
  "name": "Johan Hällgren",
  "qualified": true,
  "age": 46,
  "courses" : ["Programmering 1", "Programmering 2", "Webbutveckling 1",
    "Webbserverprogammering 1", "Tillämpad Programmering"]
}

Filen innehåller information om en lärare såsom namn, ålder, vilka kurser läraren undervisar i samt om läraren har en lärarbehörighet. Kika gärna på de olika datatyperna som finns representerade här i exemplet, det finns både string, en bool, en int och en lista med strängar. JSON tar hand om detta på ett bra sätt för att vi skall kunna slippa och typkonvertera från strängar som vi är vana vid att göra när vi normalt sätt hämtar data från en fil.

För att läsa in denna text i JSON-format från filen till vårt program så behöver vi ta hjälp av det inbyggda paketet JSON som vi importerar först i koden. Sedan använder vi inbyggda funktioner i det paketet för att läsa in filen.

json.load(f)

# Importera paketet json
import json

# encoding för att säkra upp att å,ä,ö tolkas på rätt sätt
with open("person.json", "r", encoding="utf-8") as f:
    # gör om filens innehåll till JSON-format
    t1 = json.load(f)

#Skriver ut innehållet i t1
print(type(t1), t1)

Utskriften blir då;

Utskriftsexempel

 {'name': 'Johan Hällgren', 'qualified': True, 'age': 46, 'courses': ['Programmering 1', 'Programmering 2', 'Webbutveckling 1', 'Webbserverprogammering 1', 'Tillämpad Programmering']}

Notera att datatypen boolean skrivs True eller False i python medan det skrivs true och false i JSON. Det där tar JSON-paketet hand om så det behöver du inte tänka på, men det kan vara bra att veta att det skrivs på olika sätt i de två språken så slipper du bli förvirrad.

En annan skillnad som kanske inte används lika ofta är att om vi väljer att lagra ett tomt värde för en variabel så skrivs detta None i python och null i JSON. Detta används istället för "" (tom sträng) eller "0" i de fall då man inte vill använda dessa värden. None definerar ett nullvärde, vilket inte är samma som tom sträng eller "0". Dessutom så kan man inte jämföra två null-värden utan man måste kolla om en variabel är None genom if var is None:.

Nu har vi fått se hur funktionen json.load(f) fungerar. Den laddar JSON från en fil och output från funktionen blir en dictionary. Nästa funktion att hålla koll på är json.loads(s).

json.loads(s)

s_json = '{"name": "Bo Bengtsson", "qualified": true, "age": 55, "courses": ["Matematik 1c", "Matematik 2c", "Matematik 3c"]}'
t2 = json.loads(s_json)
print(type(t2), t2)

Utskriftsexempel

<class 'dict'> {'name': 'Bo Bengtsson', 'qualified': True, 'age': 55, 'courses': ['Matematik 1c', 'Matematik 2c', 'Matematik 3c']}

Funktionen laddar JSON från en sträng, output blir typen dict.
Viktigt här att bygga strängen kring enkelfnuttar (') då både index och värden i dict-strängen måste inneslutas i dubbelfnuttar ("). Lägg också märke till att i python skriver vi True och i JSON skriver vid true, gör du på annat sätt kommer du att få felmeddelanden.

Det var två funktioner för att läsa in data från en fil eller från en sträng till JSON. Nästa steg är att lära oss att lagra JSON till fil eller till en sträng.

5.2.3.2 Skriva till fil

Nästa funktion vi behöver hålla koll på är json.dump(j, f) där vi skriver JSON till en fil.

json.dump(j, f)

# Denna koden jobbar vidare med t1 & t2 från tidigare exempel

# Lagra våra två lärar-dict i en lista
teachers = []
teachers.append(t1)
teachers.append(t2)

# Skriv ut listan till fil.
file_output = open("output_list.json", "w", encoding="utf-8")
json.dump(teachers, file_output, indent=4)
file_output.close()

# Skriv ut en lärare till fil
file_output = open("output_teacher.json", "w", encoding="utf-8")
json.dump(t1, file_output, indent=4)
file_output.close()

print(type(teachers), type(t1))

Filernas innehåll ser då ut så här;

output_list.json

[
  {
      "name": "Johan H\u00e4llgren",
      "qualified": true,
      "age": 46,
      "courses": [
          "Programmering 1",
          "Programmering 2",
          "Webbutveckling 1",
          "Webbserverprogammering 1",
          "Till\u00e4mpad Programmering"
      ]
  },
  {
      "name": "Bo Bengtsson",
      "qualified": true,
      "age": 55,
      "courses": [
          "Matematik 1c",
          "Matematik 2c",
          "Matematik 3c"
      ]
  }
]

output_teacher.json

{
  "name": "Johan H\u00e4llgren",
  "qualified": true,
  "age": 46,
  "courses": [
      "Programmering 1",
      "Programmering 2",
      "Webbutveckling 1",
      "Webbserverprogammering 1",
      "Till\u00e4mpad Programmering"
  ]
}

Några saker att lägga märke till här, ä är utbytt till \u00e4 som är unicode för tecknet ä. Sedan är det viktigt att hålla koll på att om du sedan skall läsa in detta så behöver du tänka på attt innehållet i output_teacher.json är en dictionary medan output_list.json är en lista, så här gäller det att vara noggrann.

Slutligen skall vi kolla på funktionen som omvandlar JSON till en sträng, denna strängen kan man sedan lagra till fil eller göra något annat med.

json.dumps(j)

# Denna koden jobbar vidare med t1 & t2 från tidigare exempel

# Lagra våra två lärar-dict i en lista
teachers = []
teachers.append(t1)
teachers.append(t2)

# Gör om listan till en sträng och skriver ut.
print(json.dumps(teachers))

# Gör om dictionary till en sträng och skriver ut.
print("\n",json.dumps(t1))

Utskriftsexempel

[{"name": "Johan H\u00e4llgren", "qualified": true, "age": 46, "courses": ["Programmering 1", "Programmering 2", "Webbutveckling 1", "Webbserverprogammering 1", "Till\u00e4mpad Programmering"]}, {"name": "Bo Bengtsson", "qualified": true, "age": 55, "courses": ["Matematik 1c", "Matematik 2c", "Matematik 3c"]}]

{"name": "Johan H\u00e4llgren", "qualified": true, "age": 46, "courses": ["Programmering 1", "Programmering 2", "Webbutveckling 1", "Webbserverprogammering 1", "Till\u00e4mpad Programmering"]}

På samma sätt som i förra varianten så är, ä utbytt till \u00e4 som är unicode för tecknet ä. Sedan är det viktigt att hålla koll på att om du sedan skall läsa in detta så behöver du tänka på att det ena innehållet är en lista, vilket du ser genom att innehållet finns inom list-klammrarna [] medan den andra utskriften är en dictionary.

5.2.3.3 Arbeta med dictionary / JSON

Ett okommenterat exemepel på hur man kan jobba vidare med dictionarys.

Arbeta med dictionarys

# Denna koden jobbar vidare med t1 & t2 från tidigare exempel

t3 = {}         # Tom dictionary
t3["name"] = "Anna Andersson"
t3["qualified"] = False
t3["age"] = 22
t3["courses"] = []
teachers.append(t3)

sum_age = 0
for t in teachers:
    sum_age += int(t['age'])
    print(t)

print(f"Gemensam ålder på alla lärare: {sum_age}år.")

Utskriftsexempel

{'name': 'Johan Hällgren', 'qualified': True, 'age': 46, 'courses': ['Programmering 1', 'Programmering 2', 'Webbutveckling 1', 'Webbserverprogammering 1', 'Tillämpad Programmering']}
{'name': 'Bo Bengtsson', 'qualified': True, 'age': 55, 'courses': ['Matematik 1c', 'Matematik 2c', 'Matematik 3c']}
{'name': 'Anna Andersson', 'qualified': False, 'age': 22, 'courses': []}
Gemensam ålder på alla lärare: 123år.

Man kan göra oerhört mycket med JSON/dictionarys/listor, det gäller bara att bestämma sig för en genomtänkt struktur. Skissa gärna på den, koden kommer inte lösa eventuella strukturella problem, de får du lösa först.

Frivillig uppgift för att träna på tekniken

På detta avsnitt finns det ingen uppgift, men som träning inför Projekt01 så kan du ju testa att göra om någon av de tidigare uppgifterna som utskrift/inläsning med JSON kopplat till filer. Lämpliga uppgifter är m05u11 för att träna på att lagra i fil. m05u12 fungerar bra att först lagra alla talen i en lista, lagra som JSON i en fil för att sedan läsa in filen och göra beräkningarna. Då får du träna på flera delar. Kanske utöka JSON med nya attribut för min, max och sum och sedan lagra även detta i en fil och skriva ut på lämpligt sätt.