TDDE44 Programmering, grundkurs¶

Föreläsning 3.1-3.2¶

Johan Falkenjack, johan.falkenjack@liu.se¶

Föreläsningsöversikt¶

  • Termövning 25/2
  • Laboration 3: En egen pokedex som hämtar data från webben
  • Oföränderliga och föränderliga värden
  • Variabler som referenser
  • Associationstabeller: dict
  • Mer om dataabstraktion
  • Nästlade datastrukturer
  • Bearbetning av nästlade datastrukturer
  • Strängformatering med f-strängar

Termövning 25e februari¶

  • Uppdelat i 3 pass
    • 13.15 - 14.30 Y1.a och Y1.b
    • 14.30 - 15.45 Y1.c och TMA
    • 15.45 - 17.00 MED
  • Ingen förberedelse behövs.
    • Men titta gärna på introduktionsbilderna här så kommer ni igång snabbare.
  • Samling i S26 på utsatt tid
    • Kort introduktion av mig
    • Sedan följer ni en av assistenterna till anvisad sal
  • Brukar upplevas som ett av de mest värdefulla momenten i hela kursen och har tidigarelagts i år på inrådan av förra årets studenter.

Kort om laboration 3, Del 2¶

  • Hämta information från webben istället för från fil
  • Textbaserat data-format: JSON
  • Webb-API:er - som om URL:er ("webbadresser") vore funktioner som returnerar data.
  • PokeAPI - ett webb-API till en databas om Pokémon.
  • pokedex.py - skript som skriver ut information om en Pokémon.

  • Lektion 3: övningar inför Del 2
    • paketet requests för att hämta data från webben
    • JSON-data till dictionary

Värden och referenser¶


eller:


Vad gjorde vi egentligen i kapitel 7 av Pythonuppgifterna?

Oföränderliga värden¶

  • I Python är strängar, heltal, flyttal, sanningsvärden, None och tupler oföränderliga (eng. immutable).
  • Detta betyder att man inte kan ändra på dessa värden.
    • t.ex. så kan inte ändra värdet 5 till värdet 1, värdet 5 är alltid 5.
    • Python behandlar strängar på samma sätt; i Python kan vi inte ändra strängen "hej" till strängen "nej"

Föränderliga värden¶

  • Av de datatyper som vi stött på så tillhör bara listor kategorin förändringsbara (eng. mutable) värden.
  • Detta betyder att vi kan ändra på dessa värden.
  • Vi kan t.ex. byta ut första elementet i listan [1, 2, 3] till 5
In [1]:
numbers = [1, 2, 3]
print(numbers)
numbers[0] = 5
print(numbers)
[1, 2, 3]
[5, 2, 3]
In [2]:
numbers[0] = 5
print(numbers)
[5, 2, 3]
  • Samma sak med sträng gav fel.

Jämförelse¶

numbers_tuple = (5,) + numbers_tuple[1:]
In [3]:
numbers_list = [1, 2, 3]
print(numbers_list)
numbers_list[0] = 5
print(numbers_list)
[1, 2, 3]
[5, 2, 3]
In [4]:
numbers_tuple = (1, 2, 3)
print(numbers_tuple)
numbers_tuple[0] = 5
print(numbers_tuple)
(1, 2, 3)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[4], line 3
      1 numbers_tuple = (1, 2, 3)
      2 print(numbers_tuple)
----> 3 numbers_tuple[0] = 5
      4 print(numbers_tuple)

TypeError: 'tuple' object does not support item assignment

"Ändra" oföränderliga värden¶

  • Vi använder funktionen id för att skriva ut minnesadressen för ett visst värde.
In [5]:
numbers_list = [1, 2, 3]
print("Before:", numbers_list, "at memory address", id(numbers_list))
numbers_list[0] = 5
print(" After:", numbers_list, "at memory address", id(numbers_list))
Before: [1, 2, 3] at memory address 140579410135936
 After: [5, 2, 3] at memory address 140579410135936
In [6]:
numbers_tuple = (1, 2, 3)
print("Before:", numbers_tuple, "at memory address", id(numbers_tuple))
numbers_tuple = (5,) + numbers_tuple[1:]
print(" After:", numbers_tuple, "at memory address", id(numbers_tuple))
Before: (1, 2, 3) at memory address 140579408295936
 After: (5, 2, 3) at memory address 140579408288384

Vad spelar det för roll?¶

  • Föränderliga värden
    • Snabbare (utom för väldigt stora värden).
    • Mindre säkert.
  • Oföränderliga värden
    • Långsammare (utom för väldigt stora värden).
    • Säkrare.
  • Föränderliga värden kan introducera svårhittade buggar.
  • För att förstå varför behöver vi prata om referenser.

Variabler som referenser till värden¶

  • I de flesta fall är det lämpligast att se variabler som "etiketter" som refererar till värden.
  • En tilldelningssats, t.ex. result = "", kan ses som att vi säger "låt etiketten result referera till värdet ..."
  • Liknelsen från första föreläsningen, där variabler beskrevs som lådor som innehåller värden, är alltså inte riktigt korrekt.

Variabler är inte lådor med värden i

reference.svg
  • Man säger ofta lite slarvigt att en variabel "innehåller" ett värde, och illustrerar det med att variabeln är en låda som man stoppar ett värde i.
    • (T.ex. beskrev jag det så i kursens första föreläsning.)
  • Det är mer korrekt (i Python) att säga att en variabel innehåller en referens till ett värde.
  • Flera variabler kan referera till samma värde, dvs. till samma stycke data i minnet.
  • Vi kan byta ut vilket värde som en variabel refererar till, dvs. vilket stycke data i minnet som som pekas ut, utan ändra på den data som ligger på den ursprungliga platsen i minnet.
  • Om ett värde är förändringsbart kan vi ändra på värdet som variabeln refererar till, dvs. och detta speglas i alla referenser till det värdet.
  • Det kallas för "pedagogisk finess" när en lärare ljuger på det sättet.

Flera referenser till samma värde¶

In [7]:
numbers_list = [1, 2, 3]
other_list = numbers_list
print("numbers_list is", numbers_list, "at memory address", id(numbers_list))
print("  other_list is", other_list, "at memory address", id(other_list))
numbers_list is [1, 2, 3] at memory address 140579410183168
  other_list is [1, 2, 3] at memory address 140579410183168

Konsekvenser¶

In [8]:
numbers_list = [1, 2, 3]
other_list = numbers_list
numbers_tuple = (1, 2, 3)
other_tuple = numbers_tuple

print(" numbers_list is", numbers_list, "at memory address", id(numbers_list))
print("   other_list is", other_list, "at memory address", id(other_list))
print("numbers_tuple is", numbers_tuple, "at memory address", id(numbers_tuple))
print("  other_tuple is", other_tuple, "at memory address", id(other_tuple))
 numbers_list is [1, 2, 3] at memory address 140579418594880
   other_list is [1, 2, 3] at memory address 140579418594880
numbers_tuple is (1, 2, 3) at memory address 140579783559680
  other_tuple is (1, 2, 3) at memory address 140579783559680
In [9]:
numbers_list[0] = 5
numbers_tuple = (5,) + numbers_tuple[1:]

print(" numbers_list is", numbers_list, "at memory address", id(numbers_list))
print("   other_list is", other_list, "at memory address", id(other_list))
print("numbers_tuple is", numbers_tuple, "at memory address", id(numbers_tuple))
print("  other_tuple is", other_tuple, "at memory address", id(other_tuple))
 numbers_list is [5, 2, 3] at memory address 140579418594880
   other_list is [5, 2, 3] at memory address 140579418594880
numbers_tuple is (5, 2, 3) at memory address 140579410135168
  other_tuple is (1, 2, 3) at memory address 140579783559680

Vad händer när vi skickar föränderliga värden till funktioner?¶

In [10]:
def change_and_return_list(a_list):
    a_list[0] = 5
    a_list.append("list was changed")
    return a_list

numbers_list = [1, 2, 3]
other_list = numbers_list
third_list = change_and_return_list(numbers_list)
In [11]:
print(numbers_list)
print(other_list)
print(third_list)
[5, 2, 3, 'list was changed']
[5, 2, 3, 'list was changed']
[5, 2, 3, 'list was changed']

Beror på vad funktionen gör!¶

In [12]:
def change_and_return_list(a_list):
    a_list[0] = 5
    return a_list + ["list was changed"]
 
my_list = [1, 2, 3]
other_list = my_list
third_list = change_and_return_list(my_list)
In [13]:
print(my_list)
print(other_list)
print(third_list)
[5, 2, 3]
[5, 2, 3]
[5, 2, 3, 'list was changed']

Sammanfattningsvis: Detta är klurigt och orsakar ofta buggar.¶




Förståelse och intuition kommer med övning.¶

  • Det viktiga just nu är att komma ihåg att detta är en vanlig felkälla.

Okej, andas.¶


No description has been provided for this image
  • Igen, det viktiga just nu är att komma ihåg att detta är en vanlig felkälla.

Ny datatyp: dict¶




Nycklar associerade med värden¶

  • En viktig anledning till att vi behöver ha koll på vilka datatyper som är muterbara och inte.

Dictionary

No description has been provided for this image
  • Dictionary eller dict i Python.
    • Mer generellt: associationslista eller associationstabell (eng. associative array, symbol table, map)
    • (Kärt barn har många namn.)
  • Nyckel-värde-par
  • Värden hämtas med hjälp av nyckel istället för index som i fallet med listor och tupler.
  • Alla datatyper som är oföränderliga (immutable) kan användas som nycklar, t.ex. flyttal, heltal, strängar, tupler
  • Alla datatyper kan vara värden
  • "A Dictionary of the English Language" (1755) Samuel Johnson

Varför namnet "dictionary"?¶

  • Ordet dictionary är en väldigt bra beskrivning av vad det handlar om.
    • Vi slår upp ett ord, och får den vägen veta något om ordet, t.ex. dess definition, hur det uttalas, eller den äldsta dokumenterade förekomsten av ordet.
  • Speciellt bra liknelse blir det om vi tänker oss ett klassiskt översättningslexikon, säg mellan svenska och engelska.
    • Vi slår upp ett svenskt ord, nyckeln, och får den vägen veta dess översättning, värdet, på engelska.
    • I programmeringssammanhang är vi inte bara begränsade till sträng-till-sträng-"översättning", men principen är densamma.
  • Av något skäl har den här principen visat sig förvånansvärt svår att greppa för många studenter (även för mig, för sisådär 20 år sedan).
    • Troligvis för att konceptet tenderar att introduceras vid en sådan tidpunkt att man som student förväntar sig något mycket mer komplicerat.
  • Här kommer därför ett försök till en osedvanligt rigorös beskrivning av vad det handlar om.

Matematisk funktion

square_function_xy.svg
$f: A \rightarrow B$
  • Definition: Låt $A$ och $B$ vara två mängder. En funktion $f$ från $A$ till $B$ är en relation mellan $A$ och $B$, som har egenskapen att för varje $x \in A$ finns precis ett $y \in B$ sådant att $(x, y) \in f$.
  • Vi säger saker i stil med $f: A \rightarrow B$, $x \mapsto f(x)$, och om $(x, y) \in f$ så betyder det att $y = f(x)$.
  • Detta har vi pratat om förut och är något alla förhoppningsvis är bekanta med.

Mappning som matematisk funktion¶

  • Rent logiskt ingen skillnad mellan en funktion och en mappning.
  • I praktiken uttrycks en funktion oftast som en formel.
    • Inom den matematik vi är vana vid sedan åtminstone gymnasiet, t.ex. ett polynom.
    • Inom programmering, en serie operationer som givet en viss input beräknar en viss out output.
  • Vi skulle dock kunna explicit definiera varje $x \mapsto y$
  • T.ex. för kvadraten av positiva heltal skulle det se ut som:
$$ \{1 \mapsto 1, 2 \mapsto 4, 3 \mapsto 9, 4 \mapsto 16, 5 \mapsto 25, 6 \mapsto 36, 7 \mapsto 49, \ldots\} $$

En dict för kvadrater i Python¶

  • Vi kan representera de första sju kvadraterna med en dict på följande sätt:
  • Nyckeln 1 associerad med värdet 1, nyckeln 2 associerad med värdet 4, osv.
  • Verkar otympligt och begränsande.
In [ ]:
squares = {1:1, 2:4, 3:9, 4:16, 5:25, 6:36, 7:49}
print("The square of 3 is", squares[3])
  • Det är viktigt att inse att ingen ytterligare inferens eller extrapolering görs utifrån de par som vi definierat, bara de par som explicit lagts till i dict:en kan slås upp.
    • Dvs. vi kan inte slå upp kvadraten av 8 i squares:
In [5]:
print("The square of 8 is", squares[8])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[5], line 1
----> 1 print("The square of 8 is", squares[8])

NameError: name 'squares' is not defined

En dict för kvadrater i Python¶

  • Vi kan representera de första 4 kvadraterna på följande sätt:
  • Nyckeln 1 associerad med värdet 1, nyckeln 2 associerad med värdet 4, osv.
  • Verkar otympligt och begränsande.
In [14]:
squares = {1:1, 2:4, 3:9, 4:16}
print("The square of 3 is", squares[3])
The square of 3 is 9
  • Det är viktigt att inse att ingen ytterligare inferens eller extrapolering görs utifrån de par som vi definierat, bara de par som explicit lagts till i dict:en kan slås upp.
    • Dvs. vi kan inte slå upp kvadraten av 5 i squares:
In [15]:
print("The square of 5 is", squares[5])
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[15], line 1
----> 1 print("The square of 5 is", squares[5])

KeyError: 5

Varför inte bara uttrycka det som en formel?¶

  • Kvadratfunktionen för positiva heltal går enkelt att uttrycka som en generell formel som gäller för alla positiva heltal, nämligen:
$$f(x) = x^2, x \in \mathbb{Z^+}$$
  • Dvs. i verkligheten hade vi aldrig listat dessa explicit.
  • Det finns ingen generell formel vi kan uttrycka, vi måste explicit lagra informationen för varje person.
  • Inte alla mappningar går att uttrycka lika enkelt som en formel.
  • Hur skulle t.ex. formeln för en mappning mellan personer och deras födelseår se ut?
  • Vi blir tvugna att explicit lista associationerna på något sätt:
$$ \{\text{Newton} \mapsto 1643, \text{al-Khwarizmi} \mapsto 780, \text{Gauss} \mapsto 1777, \text{Turing} \mapsto 1912, \ldots\} $$

Födelseår i Python¶

  • Skulle vi använda en vanlig Python-funktion, som vi är vana vid sedan tidigare, skulle det kunna se ut som:
  • Notera att vi använder subskript för att slå upp ett värde i en dict, inte parenteser som vi använder i funktionsanropet.
In [16]:
def birth_year_fun(name):
    if name == "Newton":
        return 1643
    elif name == "al-Khwarizmi":
        return 780
    elif name == "Gauss":
        return 1777
    elif name == "Turing":
        return 1912

print("Turing's birth year is", birth_year_fun('Turing'))
Turing's birth year is 1912
  • Som dict:
In [17]:
birth_year_dict = {"Newton": 1643, "al-Khwarizmi": 780, "Gauss": 1777, "Turing": 1912}

print("Turing's birth year is", birth_year_dict['Turing'])
Turing's birth year is 1912

En dict är en samling¶

  • Har en storlek som är antalet nyckel-värde-par.
    • Dvs. inte summan av antalet nycklar och antalet värden.
In [18]:
len({1:1, 2:4, 3:9})
Out[18]:
3
  • En dict är dock inte en sekvens, dvs. vi kan inte använda index eller slice för att slå upp värden.
  • Subskriptnotationen används istället för att slå upp värdet associerat med en viss nyckel:
    • T.ex. uttrycket birth_year_dict['Turing'] beräknas till 1912.

En dict är förändringsbar¶

  • Dvs. vi får göra nedanstående om vi skulle komma fram att 780 var fel födelseår för al-Khwarizmi.
    • (I verkligheten vet vi inte exakt vilket år al-Khwarizmi föddes, bara att det var runt 780.)
birth_year_dict['al-Khwarizmi'] = 779
  • Vi kan också lägga till nya nyckel-värde-par genom tilldelning på följande vis:
birth_year_dict['Cantor'] = 1845

Nycklarna måste vara unika¶

  • Bara det sista nyckel-värde-paret med en viss nyckel kommer att lagras.
In [19]:
hcs_surnames = {"Erik": "Prytz", "Erik": "Berglund"}
print(hcs_surnames)
{'Erik': 'Berglund'}
  • Men inte värdena
    • Flera nycklar kan ha samma värde:
In [20]:
hcs_firstnames = {"Prytz": "Erik", "Berglund": "Erik"}
print(hcs_firstnames)
{'Prytz': 'Erik', 'Berglund': 'Erik'}

En nyckel kan ha flera värden¶

  • Men vi måste i så fall explicit representera det med ett värde som är en samling, t.ex. en lista:
In [21]:
hcs_surnames = {"Erik": ["Prytz", "Berglund"]}
print(hcs_surnames)
{'Erik': ['Prytz', 'Berglund']}

Nycklar måste vara hashningsbara¶

  • Bara vissa datatyper kan användas som nycklar, de som är hashningsbara.
  • Vi kan tänka på hashningsbar som ekvivalent med oföränderlig för tillfället.
    • Dvs. det går bra att ha nycklar som är int, tuple, str men inte nycklar som är list, eller för den delen dict.
  • Alla datatyper kan dock användas som värden.
  • Vi kan också blanda olika datatyper som både nycklar och värden i sammma dict:
In [22]:
dict_example = {"name": "Cantor", 345: "integer", 3: 54, (4,9): ['occupied', 'working', 'forbidden']}
  • (Men det blir ofta svårt att reda ut vad i hela fridens namn datan egentligen representerar när vi blandar friskt.)

Iterera över en dict¶

  • Metoderna dict.keys(), dict.values() och dict.items() returnerar olika vyer (eng. views).
  • Vyer tillåter att vi itererar över
    • nycklar: dict.keys()
    • värden: dict.values()
    • nyckel-värde-par som tupler med 2 element: dict.items()
    • ("dict" refererar här till ett värde av typen dictionary, denna konvention, att låta datatypens namn stå för ett godtyckligt värde av typen, används även i pythondokumentationen)
  • Vi kan dock inte använda index eller slice på en vy, och vyer kan inte användas för att ändra i den dict den är kopplad till.

OBS! Iterera inte i onödan!¶

  • T.ex. behöver vi aldrig iterera för att kontrollera om en nyckel finns i en dict (my_key in my_dict) eller för att slå upp värdet associerat med en känd nyckel (my_dict[my_key]).
  • Många lurar sig att de måste hålla på och loopa och leta i dict:ar eftersom de just lärt sig att iterera.
  • I många fall är poängen med dict att slippa att iterera eller leta manuellt.

Iterera över nycklar¶

In [23]:
birth_years = {"Newton": 1643, "al-Khwarizmi": 780, "Gauss": 1777, "Turing": 1912}
for name in birth_years.keys():
    print(name)
    
Newton
al-Khwarizmi
Gauss
Turing

Iterera över värden¶

In [24]:
birth_years = {"Newton": 1643, "al-Khwarizmi": 780, "Gauss": 1777, "Turing": 1912}
for year in birth_years.values():
    print(year)
    
1643
780
1777
1912

Om vi vill ha både nycklar och värden då?

  • Dåligt sätt:
  • Väldigt vanligt, men inte rekommenderat.
In [25]:
birth_years = {"Newton": 1643, "al-Khwarizmi": 780, "Gauss": 1777, "Turing": 1912}
for name in birth_years.keys():
    print(name, "was born in", birth_years[name])
    
Newton was born in 1643
al-Khwarizmi was born in 780
Gauss was born in 1777
Turing was born in 1912

Om vi vill ha både nycklar och värden då?

  • Bättre sätt:
  • Notera att vi kan göra tuple assignment när varje element är en tupel och på så sätt explicit arbeta med två loopvariabler samtidigt.
  • Många gånger snabbare och mer "Pythonic"
In [26]:
birth_years = {"Newton": 1643, "al-Khwarizmi": 780, "Gauss": 1777, "Turing": 1912}
for pair in birth_years.items():
    print(pair)
    
('Newton', 1643)
('al-Khwarizmi', 780)
('Gauss', 1777)
('Turing', 1912)
In [27]:
# Med tuple assignment av 2 loopvariabler i for-loopen:
for name, year in birth_years.items():
    print(name, "was born in", year)
    
Newton was born in 1643
al-Khwarizmi was born in 780
Gauss was born in 1777
Turing was born in 1912

Sammansatta datastrukturer¶

Fram tills nu¶

  • Oftast en variabel för varje värde:
In [28]:
pokemon_name = "Pidgey"
ability1 = "big-pecks"
ability2 = "tangled-feet"
ability3 = "keen-eye"
  • Hur gör vi om vi vill ha information om fler eller färre "abilities" på ett smidigt sätt?

Lista för relaterade data¶

  • Vi har sett att listor kan användas att representera data som kan innehålla fler än ett värde, t.ex.
abilities = ["big-pecks", "tangled-feet", "keen-eye"]
In [29]:
# Before:
pokemon_name = "Pidgey"
ability1 = "big-pecks"
ability2 = "tangled-feet"
ability3 = "keen-eye"

# After:
pokemon_name = "Pidgey"
abilities = ["big-pecks", "tangled-feet", "keen-eye"]
  • Men hur gör vi om vi vill ha information om flera Pokémon? Ska vi ha pokemon_name1, pokemon_name2, abilities1, abilities2?

Listor i listor¶

  • En lista kan själv vara ett element i en annan lista.
  • Vi kallar detta för nästling, en inre lista nästlad inne i en yttre lista.
    • Detta är en typ av rekursiv datastruktur.
  • Vi kan samla ihop informationen om varje Pokémon till en lista samt bestämma den ordning som informationen ska komma i.
    • Första elementet kan vara namnet och andra elementet den tillhörande listan med "abilities".
pokemon1 = ["Pidgey", ["big-pecks", "tangled-feet", "keen-eye"]]
pokemon2 = ["Ditto", ["imposter", "limber"]]
In [30]:
# Before:
pokemon_name = "Pidgey"
abilities = ["big-pecks", "tangled-feet", "keen-eye"]

# After:
pokemon1 = ["Pidgey", ["big-pecks", "tangled-feet", "keen-eye"]]
pokemon2 = ["Ditto", ["imposter", "limber"]]
  • Men hur gör vi om vi inte vet exakt hur många Pokémon vi kommer behöva lagra data om? Vi kan ju inte skapa hundratals variabler som heter pokemonN för godtyckligt stora värden på N.

Samma princip igen¶

  • Vi kan också samla ihop alla Pokémon-listor i en lista.
  • Nu kan vi helt enkelt lägga till nya listor i vår lista.
pokemon = [["Pidgey", ["big-pecks", "tangled-feet", "keen-eye"]], ["Ditto", ["imposter", "limber"]]]
In [31]:
# Before:
pokemon1 = ["Pidgey", ["big-pecks", "tangled-feet", "keen-eye"]]
pokemon2 = ["Ditto", ["imposter", "limber"]]

# After:
pokemon = [["Pidgey", ["big-pecks", "tangled-feet", "keen-eye"]], ["Ditto", ["imposter", "limber"]]]

Göra nästlade listor mer överskådliga¶

  • Vi kan använda radbrytningar och indentering för att visualisera strukturen hos en nästlad lista:
pokemon = [ 
    [
        "Pidgey", 
        ["big-pecks", "tangled-feet", "keen-eye"]
    ], 
    [
        "Ditto", 
        ["imposter", "limber"]
    ]
]
In [32]:
# Before:
pokemon = [["Pidgey", ["big-pecks", "tangled-feet", "keen-eye"]], ["Ditto", ["imposter", "limber"]]]

# After:
pokemon = [ 
    [
        "Pidgey", 
        ["big-pecks", "tangled-feet", "keen-eye"]
    ], 
    [
        "Ditto", 
        ["imposter", "limber"]
    ]
]
  • Men hur ska vi veta vad som är vad. Om du är bekant med Pokémon kanske du vet att "pidgey" är ett namn och känner igen olika typer av "abilities", men för oss andra då?

Vår nya datatyp till räddning¶

  • Vi ersätter listan för varje Pokémon med en dict med beskrivande namn på nycklarna:
pokemon = [ 
    { 
        "name": "Pidgey",
        "abilities": ["big-pecks", "tangled-feet", "keen-eye"]
    },
    {
        "name": "Ditto",
        "abilities": ["imposter", "limber"]
    }
]
In [33]:
# Before:
pokemon = [ 
    [
        "Pidgey", 
        ["big-pecks", "tangled-feet", "keen-eye"]
    ], 
    [
        "Ditto", 
        ["imposter", "limber"]
    ]
]

# After:
pokemon = [ 
    { 
        "name": "Pidgey",
        "abilities": ["big-pecks", "tangled-feet", "keen-eye"]
    },
    {
        "name": "Ditto",
        "abilities": ["imposter", "limber"]
    }
]
  • Nu måste vi dock loopa för att hitta data om en viss Pokémon, det hade ju varit bättre om vi kunde slå upp dem direkt.

Vi tillämpar åter samma princip igen¶

  • Men med dictistället.
  • Vi kan använda själva namnen som nycklar också.
pokemon = {
    "pidgey": {
        "name": "Pidgey",
        "abilities": ["big-pecks", "tangled-feet", "keen-eye"]
    },
    "ditto": {
        "name": "Ditto",
        "abilities": ["imposter", "limber"]
    }
}
  • Redundant att ha namn som nyckel och sen en inre dict med samma namn som värde?
  • Kanske, men i många fall har vi ett kortnamn och ett fullständigt namn.
In [34]:
# Before:
pokemon = [ 
    { 
        "name": "Pidgey",
        "abilities": ["big-pecks", "tangled-feet", "keen-eye"]
    },
    {
        "name": "Ditto",
        "abilities": ["imposter", "limber"]
    }
]

# After:
pokemon = {
    "pidgey": {
        "name": "Pidgey",
        "abilities": ["big-pecks", "tangled-feet", "keen-eye"]
    },
    "ditto": {
        "name": "Ditto",
        "abilities": ["imposter", "limber"]
    }
}

JSON¶

  • JSON, eller JavaScript Object Notation är ett dataformat som speglar nästan exakt nästlade dict:ar och listor i Python.
  • Modulen json kan läsa in en fil i JSON-format och representera den just som nästlade dict:ar och listor.
  • Svaret vi får från ett Webb-API kommer väldigt ofta i just JSON-format.
  • https://www.json.org/json-sv.html

JSON: Exempel¶

  • Konfigurationsfil för Pythonuppgifterna
{
    "excluded_shortcodes": [],
    "excluded_misc": [],
    "pytest_config": {
        "VENV_PATH": "/courses/TDDE44/venv_pytest",
        "HELP_URL": "https://www.ida.liu.se/~TDDE44/kurslogistik/inlamningar/"
    },
    "assignment_sets": {
        "intro": {
            "name": "Introduktion",
            "shortname": "Introduktion",
            "outcomes": [
                "Förstå vad funktioner är och hur de används.",
                "Förstå vad ett returvärde är.",
                "Känna till olika datatyper så som till exempel heltal, flyttal och strängar och veta hur man konverterar mellan dem.",
                "Kunna använda python som en miniräknare."
            ],
            "pass_score": 0.7,
            "distinction_score": 0.7,
            "point_rounding": 5,
            "assignments": [
                {
                    "type": "single",
                    "id": "basic/return_five",
                    "points": 5
                },
                {
                    "type": "single",
                    "id": "basic/five_twice",
                    "points": 5
                },
...
  • Säger inte nödvändigtvis så mycket om man inte vet hur systemet som genererar pythonuppgifterna fungerar i övrigt.

JSON: Exempel¶

  • Tentakonfiguration
{
  "id": "TDDE44-DAT1-20260107",
  "version": "1.3",
  "config-type": "single",
  "instructions": "TDDE44_v10.md",
  "title": "TDDE44. DAT1. 2hp.",
  "date": "2026-01-07 kl. 14.00-18.00",
  "templates": {
    "exam": "exam_template.md",
    "part": "part_template.md",
    "single_question": "single_question_template_manual_points.md",
    "multi_question": "multi_question_template_manual_points.md",
    "subquestion": "subquestion_template_manual_points.md",
    "web_solution_exam": "hugo_web_solution_template.md",
    "web_solution_part": "part_template.md",
    "web_solution_single_question": "single_question_template_web_with_solution.md",
    "web_solution_multi_question": "multi_question_template_web_with_solution.md",
    "web_solution_subquestion": "subquestion_template_web_with_solution.md"
  },
  "pagebreak_after_question": true,
  "unittests": "question",
  "parts": [
    {
      "name": "Del 1",
      "id": "del1",
      "info": "För att bli godkänd på Del 1 måste alla uppgifter vara godkända.",
      "questions": [
        {
          "name": "Uppgift 1 - Loopar",
          "id": "uppg1",
          "type": "multi",
          "points": "",
          "info": "- För att få godkänt på denna uppgift skall __båda__ deluppgifterna lösas.\n- En av uppgifterna skall lösas med en `while`-loop och en skall lösas med `for`-loop.\n- Comprehensions, generatorer eller rekursion får *inte* användas för att lösa uppgifterna.\n- Spara lösningarna till båda deluppgifterna i Uppgift 1 i en fil med namnet `uppg1.py`.\n- Filen skickas in via Studentklienten som '*Assignment #1*'.",
          "required_score": 4,
          "global_tests": [
            {
              "name": "Uppgift 1 - en med for, en med while",
              "id": "test1",
              "points": "2",
              "file": "for_while"
            }
          ],
          "subquestions": [
            {
              "name": "Uppgift 1.1 - lös med lämplig loop",
              "id": "uppg11",
              "type": "single",
              "points": "",
              "folder": "listor",
              "file": "get_repeated"
            },
            {
              "type": "pagebreak"
            },
            {
              "name": "Uppgift 1.2 - lös med lämplig loop",
              "id": "uppg12",
              "type": "single",
              "points": "",
              "folder": "loopar",
              "file": "multiply_until_inc"
            }
          ]
        },
...
  • Samma sak här, man behöver veta hur systemet fungerar för att förstå alla delar av själva datarepresentationen.