Göm meny
Gäller för: HT25

Förberedelsematerial 2.2


Inför seminariet ska du gå igenom materialet nedan, vilket bör ta ca en timme, samt genomföra det tillhörande quizet som testar dina kunskaper på materialet. Quizet finns endast till för att du ska kunna skatta dina egna kunskaper inför seminariet, och är inget du bedöms på eller ens sparas.

Iteration

Under storseminarie 2.1 bearbetade vi sekvenser med hjälp av rekursion när vi simulerade viskleken. Vi stegade igenom, eller traverserade, listan med namn och för varje rekursivt anrop utförde vi en operation på det första elementet.

Detta fungerar på relativt korta sekvenser (under 1 000 element i normala fall). En nackdel med rekursion är dock att vi gör väldigt många rekursiva funktionsanrop, som alla hamnar på anropsstacken. Anropsstacken har en maximal storlek och det innebär att vi också bara kan hantera en maximal mängd element i en sekvens på det här sättet.

Olika språk och programmeringsparadigm (sätt att bygga program) löser det här problemet på olika sätt. Det vanligaste sättet när vi bearbetar en sekvens, och sättet vi kommer fokusera på här, är att istället för rekursion använda iteration.

Python har två huvudsakliga konstruktioner för att utföra iteration, for och while. Vi kommer börja med for eftersom det är den konstruktion vi oftast vill använda oss av när vi bearbetar just sekvenser.

Iteration över en sekvens med for

Se exemplet nedan

99 bottles of beer on the wall, 99 bottles of beer.
Take one down, pass it around. 98 bottles of beer.
98 bottles of beer on the wall, 98 bottles of beer.
Take one down, pass it around. 97 bottles of beer.
97 bottles of beer on the wall, 97 bottles of beer.
Take one down, pass it around. 96 bottles of beer.
96 bottles of beer on the wall, 96 bottles of beer.
Take one down, pass it around. 95 bottles of beer.
95 bottles of beer on the wall, 95 bottles of beer.
Take one down, pass it around. 94 bottles of beer.
94 bottles of beer on the wall, 94 bottles of beer.
Take one down, pass it around. 93 bottles of beer.
93 bottles of beer on the wall, 93 bottles of beer.
Take one down, pass it around. 92 bottles of beer.
...

Om vi vill skapa ett program som genererar denna utskrift men börjar med 1 500 flaskor så kommer vår rekursiva approach inte att fungera. Kopiera koden nedan och testa med olika argument till song_rec. Ungefär hur stora värden kan man skicka in utan att programmet ger ett RecursionError?

1
2
3
4
5
6
7
def song_rec(i):
   if i > 0:
       print(f"{i} bottles of beer on the wall, {i} bottles of beer")
       print(f"Take one down, pass it around, {i-1} bottles of beer on the wall")
       song_rec(i - 1)

song_rec(100)

Istället kan vi använda en for-loop. Syntaxen för for-loopen är:

1
2
for loop_variable in iterable_data:
    do_something(loop_variable)

Till skillnad från i den rekursiva approachen så kallar vi inte på en funktion i varje steg och begränsas därför inte heller av storleken på anropsstacken. Istället upprepas kodblocket tillhörande for-satsen en gång för varje element i iterable_data. För varje upprepning tilldelas loop_variable värdet av nästa element i iterable_data, som kan vara vilken typ av data som helst som är iterabel.

Vilka datatyper detta faktiskt är kommer du få en känsla för, men av de datatyper vi stött på hittills är det strängar (str), listor (list) och tupler (tuple). Mer generellt kan man säga att alla datatyper som är sekvenser är iterabla.

Exempelvis:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
song_lines = [
    "99 bottles of beer on the wall, 99 bottles of beer.\nTake one down, pass it around. 98 bottles of beer.",
    "98 bottles of beer on the wall, 98 bottles of beer.\nTake one down, pass it around. 97 bottles of beer.",
    "97 bottles of beer on the wall, 97 bottles of beer.\nTake one down, pass it around. 96 bottles of beer.",
    "96 bottles of beer on the wall, 96 bottles of beer.\nTake one down, pass it around. 95 bottles of beer.",
    "95 bottles of beer on the wall, 95 bottles of beer.\nTake one down, pass it around. 94 bottles of beer.",
    "94 bottles of beer on the wall, 94 bottles of beer.\nTake one down, pass it around. 93 bottles of beer.",
    "93 bottles of beer on the wall, 93 bottles of beer.\nTake one down, pass it around. 92 bottles of beer.",
]


for line in song_lines:
    print(line)

Resultatet kommer motsvara texten i exemplet ovan.

Detta leder dock till ett litet problem: Om vi vill börja med 1 500 flaskor så behöver vi göra en lista med 1500 element. Vi kan dock lösa detta genom att fokusera på själva siffrorna eftersom det är det enda som förändras mellan verserna i sången.

Datatypen range

I pythonuppgifterna för rekursion stötte vi på datatypen range och vi implementerade en serie funktioner som skapade listor med motsvarande beteende fast med listor. Datatypen range representerar en av sekvens av heltal från ett startvärde (0, om inget annat anges) till ett slutvärde, eventuellt med en steglängd.

1
range(1500)

Anropet ovan ger oss en sekvens (som en lista eller tupel) med 1500 värden, från 0 till 1499. Vi kan sedan iterera över denna sekvens och använda dess element hur vi vill. Stoppvärdet är exklusivt (jämför med index-värden i en sekvens). Vi kan därför använda argumentet 1501 för att få alla värden från 0 till och med 1500:

1
2
for number in range(1501)
    print(number)
0
1
2
3
4
.
.
.
1499
1500

Vi ville ju egentligen ha heltalen från 1500 till och med 1 men vad vi har åstadkommit är heltalen från 0 till och med 1500. Det var ju inte riktigt vad vi ville ha, men den som är lite finurlig ser kanske redan andra sätt att lösa detta.

Dels kan vi använda enkel aritmetik för att översätta sekvensen vi fick först, 0 till och med 1499, till sekvensen vi vill ha, 1500 till och med 1. Dvs. vi kan ändra inne i loopkroppen från print(number) till print(1500-number).

Vi vet ju också att range är en sekvens, så vi kan använda subskriptnotationen som vi sett för strängar, listor och tupler för att göra ett utsnitt. Vi kan vända på en sekvens med hjälp av subskriptet [::-1]range(1500)[::-1] kommer motsvara 1499 till och med 0. Sedan kan vi använda number+1 för att få värdena 1500 till och med 0.

Båda dessa approacher är dock otympliga i jämförelse med att säga åt range att ge oss rätt sekvens direkt. Vi slår upp range i dokumentationen med hjälp av funktionen help:

1
>>> help(range)
class range(object)
 |  range(stop) -> range object
 |  range(start, stop[, step]) -> range object
 |
 |  Return an object that produces a sequence of integers from start (inclusive)
 |  to stop (exclusive) by step.  range(i, j) produces i, i+1, i+2, ..., j-1.
 |  start defaults to 0, and stop is omitted!  range(4) produces 0, 1, 2, 3.
 |  These are exactly the valid indices for a list of 4 elements.
 |  When step is given, it specifies the increment (or decrement).
...

På andra och tredje raderna ser vi två olika sätt att anropa range och på rad 5-9 förklaras hur argumenten tolkas. Skickar vi bara med ett argument blir det automatiskt stoppvärde och startvärdet blir 0, men skickar vi med 2 eller 3 argument kan vi vara mer flexibla. Vad dokumentationen betyder är att vi kan skriva range(1500, 0, -1) för att få heltalen från 1500 till 1 med ett steg i negativ riktning för varje element.

1
2
3
for number in range(1500, 0, -1):
    print(f"{number} bottles of beer on the wall, {number} bottles of beer.")
    print(f"Take one down, pass it around. {number-1} bottles of beer.")

~ Testa gärna själv och se vad som händer! ~

En mer generell version av skriptet ovan får vi genom att skapa en funktion:

1
2
3
4
5
6
def song(number_of_verses):
    for number in range(number_of_verses, 0, -1):
        print(f"{number} bottles of beer on the wall, {number} bottles of beer.")
        print(f"Take one down, pass it around. {number-1} bottles of beer.")

song(99)

Dokumentationsövning: Läs på om range i pythondokumentationen här. Du känner kanske att du redan förstår hur range fungerar, men du ska se detta som en övning i att läsa pythondokumentationen. Det kommer göra det lättare att slå upp saker vid behov på duggan.

Villkorsstyrd iteration med while

Vi har sett att for-loopen fungerar jättebra när vi har en sekvens att iterera över eller kan specificera en sådan sekvens med hjälp av range.

Ibland kan vi dock inte säga i förväg hur många gånger ett stycke kod behöver upprepas. I de fallen behöver vi ett mer generellt sätt att åstadkomma upprepning. Vi har sett att rekursion är ett sådant mer generellt sätt (stoppvillkoret kan ju vara vad som helst), men att det inte är lämpligt när vi måste göra många upprepningar. I de här fallen kan vi använda while-loopen.

Vi kan se while-loopen som en generalisering av villkorssatsen if. Istället för att bara utföra koden i det tillhörande blocket en gång, om villkoret är sant, så upprepas koden i blocket så länge som villkoret är sant. Med andra ord kan vi läsa ut det som “så länge som sanningsuttrycket är sant utför följande”.

Syntaxen för en while-loop ser ut som följande:

1
2
while <uttryck som beräknas till ett sanningsvärde>:
    do_something()

Exempelvis:

1
2
while True:
    print("It never ends!")

Denna loop kommer fortsätta för evigt om vi inte avbryter den manuellt. Vi kallar det för en oändlig loop och det är en mycket vanlig bugg, speciellt när man är nybörjare.

Hamnar du i situationen att ditt program bara står och tuggar och aldrig kommer vidare eller avslutas och du använt en while-loop så är det troligt att du råkat skapa en oändlig loop. Tryck i så fall “Ctrl+C” för att avbryta loopen. Man kan också använda “Ctrl+D” för att avbryta en process i ett terminalfönster.

För att undvika oändliga loopar vill vi i första hand använda for när det går eftersom vi i det fallet nästan alltid kan vara säkra på att loopen kommer avslutas. Är det inte möjligt så får vi skriva vårt villkor på ett sådant sätt att det förr eller senare blir falskt.

Ett vanligt exempel som upprepar något exakt 10 gånger:

1
2
3
4
counter = 0
while counter < 10:
    ...
    counter = counter + 1

Denna loop kommer utföra loopkroppen 10 gånger och sen gå vidare när värdet på counter blir lika med 10 eller mer. Här kan vi se att vi med hjälp av while-loopen har återskapat beteendet hos en for loop över range(10) och det gäller generellt att vi alltid kan skriva om en for-loop till en while-loop på ungefär det här sättet.

Testa gärna kodexemplet genom att skriva print(f"The counter is {counter}") istället för ... och testa programmet.

När ska man använda while?

I exemplet ovan definierade vi counter innan loopen, jämförde värdet av counter manuellt i loopvillkoret, och räkna upp värdet av counter manuellt inne i loopkroppen. Detta innebär 3 olika ställen vi skulle kunna göra något fel jämfört med om vi definierat motsvarande range att loopa över med for:

1
2
for counter in range(10):
    ...

Det är alltså lätt att tänka att for alltid är att föredra framför while, och det är i någon mening sant. Kan vi skriva en for-loop så är det oftast enklare, mer lättläst, och innebär mindre risk för att introducera buggar.

Så varför finns while-loopen om for-loopen alltid är att föredra? Tyvärr är det inte alltid möjligt att använda en for-loop.

En typisk situation när vi behöver använda while är när antalet iterationer vi behöver utföra inte kan förutsägas på grund av någon form av slumpmässighet. Säg t.ex. att vi vill simulera slag av en 6-sidig tärning tills vi slår en 6:a. Hur gör vi det med en for-loop när vi inte vet hur många gånger vi behöver slå tärningen innan sidan 6 kommer upp? Vi kan inte beskriva sekvensen av tärningsslag innan de faktiskt har skett och alltså kan vi inte heller använda en for-loop för att iterera över den sekvensen.

I teorin kan vi kanske säga att om vi gör tillräckligt många iterationer så kommer vi till slut att slå en 6:a, så vi skulle kunna loopa över range(99999999) och avbryta (se nedan) när vi slagit en 6:a. Detta är dock inte garanterat att räcka, speciellt inte när villkoret för att avbryta loopen är mer ovanligt. Vi behöver dessutom göra en manuellt kontroll med en if-sats för att se om det är dags att avbryta, men om vi ändå måste göra det så kan vi ju lika gärna använda det villkoret (eller motsatsen till det) som villkor i en while.

En annan vanligt förekommande situation är när vi vill utföra en mer komplex beräkning som innebär att vi behöver applicera samma formel tills vi uppnått ett resultat som uppfyller något villkor. Det kan t.ex. handla om att beräkna antalet år det tar att betala av ett lån givet en viss ränta och viss amorteringstakt (hur mycket man betalar tillbaka varje år.)

I många fall kan en duktig matematiker, ekonom eller ingenjör beskriva detta med en lite mer avancerad matematisk formel som slår ihop alla steg till en enda beräkning. Vi programmerare behöver dock bara kunna räkna ut ett steg, vilket oftast är jämförelsevis enkelt, och sedan låter vi iteration göra resten av jobbet. Poängen är dock att vi inte i förväg vet hur många iterationer som krävs, dvs. hur många gånger loopen behöver upprepas, innan vi har nått resultatet och därför kan vi inte heller använda en for-loop utan får använda while-loopen istället

En tredje vanlig situation när en for-loop inte går att använda är när vi har någon form av interaktion med en användare som påverkar hur många gånger loopen ska upprepas. Rent tekniskt kan vi betrakta detta på samma sätt som om processen vore slumpmässig, eftersom vi inte vet i förväg hur användaren kommer att agera (speciellt om vi, som examinatorn, är lite Bayesianska och betraktar nästan alla former av “slump” som avsaknad av visshet/förutsägbarhet, men det hör inte hit, mer om det i AI-kursen).

För den som är nyfiken eller lite mer erfarna programmeraren: Tekniskt sett kan vi använda en for-loop i alla dessa fall också. Vi behöver dock i så fall implementera en egen iterator. En iterator är ett objekt som stegvis genererar nästa värde i en sekvens utan att behöva lagra hela sekvensen i minnet, på samma sätt som range fungerar. Istället för att bara generera nästa tal i en serie av heltal, som range gör, så kan vi definiera beräkningen av nästa steg på samma sätt som vi hade gjort i while-loopens kropp. Detta är dock oftast onödigt komplicerat och svårt att tolka och därför inte heller något som är rekommenderat att göra annat än i särskilda fall som ligger ännu längre utanför kursens ramar.

Avbryta eller hoppa över varv

Vi kan hoppa direkt till nästa iteration utan att slutföra loopblocket genom att använda nyckelordet continue. Detta avbryter inte loopen, men eventuella satser efter continue i loopblocket kommer alltså inte att utföras för den aktuella iterationen.

Vi kan också avbryta en pågånde iteration helt med hjälp av nyckelordet break (vi kan också returnera inne i en loop, vilket automatiskt avbryter loopen iom att hela funktionen avslutas vid return).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
count = 0
while count < 10:
    if count == 3 or count == 5:
        count += 1
        continue

    if count > 7:
        print("Nu orkar jag inte mer." + "(count: " + str(count) + ")")
        break
    elif count > 3:
        print("Är vi framme snart?" + "(count: " + str(count) + ")")
    elif count > 4:
        print("Har vi kört vilse?" + "(count: " + str(count) + ")")
    else:
        print("Det här är roligt!" + "(count: " + str(count) + ")")
    count += 1

~ Titta på koden och försök att räkna ut vad som kommer skrivas ut. Testkör sedan koden. Hade du rätt? Om inte, varför blev det inte som du tänkt? ~

För den som är nyfiken

På Youtube: The Fastest Way to Loop in Python - An Unfortunate Truth

Referenser, objekt, metoder och muterbarhet

Det kan verka som att det här avsnittet innehåller väldigt många olika saker, men eftersom de hänger samman har vi valt att introducera dem samtidigt. Läs detta avsnitt översiktligt och gå tillbaka till det medan ni arbetar med motsvarade kapitel av Pythonuppgifterna.

Variabler som namngivna lådor
som innehåller värden.

Vi har tidigare pratat om variabler som lådor med namn på i vilka det ligger värden. Detta är en bra metafor att börja med, men tyvärr stämmer den inte riktigt hela vägen. Vi har ju redan tidigare sett att vi kan skicka med en variabel som argument till en funktion. Då har variabelns värde också bundits till namnet på motsvarande parameter i funktionskroppen. Redan här är det ju något som är lite skakigt med vår idé om att en variabel är en låda som innehåller ett värde. Värdet ligger ju nämligen fortfarande kvar i den där ursprungliga lådan när funktionen kört färdigt.

Så vad var det egentligen som hände när vi anropade en funktion? Skapade vi en ny låda med en kopia av det ursprungliga värdet? Det är absolut ett sätt att tänka på det och passar ju väldigt bra med att vi skapade nya stack-frames på anropsstacken att lagra våra lokala variabler i varje gång vi anropade en funktion. Det här sättet att binda parametrar till argument brukar kallas call-by-value (eller pass-by-value) och många äldre språk och som ligger närmare hårdvaran, som t.ex. C, använder strikt call-by-value. Allt vi har gjort hittills i kursen har betett sig som om även Python fungerade enligt call-by-value. Det beror dock på att vi hittills har sett och behandlat alla värden som om de vore oföränderliga, eller immutable.

Variabler som namngivna lådor
som innehåller referenser.

Att ett värde är oföränderligt, eller immutable, betyder att vi inte kan ändra på värdet. Varje gång vi ändrat på en variabel så har vi ju gjort det genom tilldelning, dvs. vi har bundit variabelnamnet till ett nytt värde. Vi kan tänka på detta som att variabler inte är lådor med värden, utan snarare lådor med referenser till värden. En referens är helt enkelt en minnesadress som pekar ut ett stycke minne där variabelns värde ligger lagrat. När vi ändrar på en variabel genom att göra en ny tilldelning så ändrar vi vilken minnesadress som variabeln pekar ut, eller refererar till.

När vi gör ett funktionsanrop i Python så är det den här referensen som kopieras och det är det som tillåter att olika namn kan “referera” till samma värde. När både argument och parametrarna de binds till är referenser så kallar vi det för call-by-reference (eller pass-by-reference).

Men vad har detta att göra med föränderlighet (eller muterbarhet som vi ofta säger efter engelskans mutability)? Vi ska först titta på hur man faktiskt ändrar ett föränderligt värde.

Listan som föränderlig datatyp

Vi har tidigare tittat på både listor och tupler och betraktat dem som jämförbara med varandra. Nu ska vi dock titta på skillnaden mellan dem. Som ni kanske gissat från så består skillnaden av att listor är föränderliga (mutable) medan tupler är oföränderliga (immutable). Dvs. vi kan lägga till eller ta bort element ur en lista utan att behöva skapa en ny lista och göra en ny tilldelning.

Den enklaste operation som går att göra med en lista men inte en tupel är att tilldela ett element. Testa att köra koden nedan. I båda exemplen använder vi en utökning av f-strängsnotationen vi introducerade förra veckan. Om vi i ett uttryck insprängt i en f-sträng lägger till ett =-tecken på slutet så kommer både själva uttrycket och dess värde att skrivas ut. Detta är väldigt praktiskt för att inspektera värdet av variabler när man gör spårutskrifter.

1
2
3
4
my_list = [1, 2, 3]
print(f"{my_list=}")
my_list[0] = 42
print(f"{my_list=}")

Prova nu motsvarande med en tupel och notera felmeddelandet:

1
2
3
4
my_tuple = (1, 2, 3)
print(f"{my_tuple=}")
my_tuple[0] = 42
print(f"{my_tuple=}")

Även om det innebär en stor skillnad bara att vi kan byta ut värden i listor så kan vi faktiskt göra så mycket mer än bara det. Vi kan ändra på storleken av en lista genom att ta bort eller lägga till. Det första vi kan titta på är att ta bort ett element ur en lista. För att göra det använder vi nyckelordet del (för delete) som säger att något ska tas bort. Vi kan använda del för att ta bort en variabel, men här ska vi istället använda subskriptnotation för att peka ut vilket eller vilka element som ska tas bort ur den lista en viss variabel pekar på.

1
2
3
4
my_list = [1, 2, 3]
print(f"{my_list=}")
del my_list[1]
print(f"{my_list=}")

Notera att det går att ta bort såväl enskilda element genom att peka ut ett index som en samling element genom att använda slice. Prova t.ex. att köra

1
2
3
my_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
del my_list[::2]
print(f"{my_list=}")

Prova också att ta bort ett enskilt element ur en tupel.

När vi har flera referenser till samma värde

Prova att köra följande stycke kod:

1
2
3
4
5
6
7
list_a = [1, 2, 3, 4]
list_b = list_a
print(f"{list_a=}")
print(f"{list_b=}")
del list_b[2]
print(f"{list_a=}")
print(f"{list_b=}")

Notera att både list_a och list_b har förändrats efter att del list_b[2] exekverades.

Kom ihåg frågan vi ställde oss ovan, vad har referenser med muterbarhet att göra? Jo, om ett värde som en referens pekar ut är muterbart, och vi har ytterligare en referens till samma värde, så innebär att om vi ändrar på värdet genom den ena referensen, så kan vi se förändringen även med den andra referensen. Båda referenserna pekar ju ut samma stycke minne, och det är det minnet vi i så fall har ändrat i. Detta är exakt vad vi ser exempel på ovan med variablerna list_a och list_b.

Detta kan verka som ett konstigt fall som inte kommer inträffa särskilt ofta och inte bör orsaka några större problem, men kom ihåg att vi kom in på detta eftersom vi pratade om hur funktionsanrop gick till.

Antag att vi vill skapa en funktion som ger oss alla udda värden i en lista. Ett sätt att göra det skulle vara att helt enkelt ta bort alla jämna värden ur listan:

1
2
3
4
5
6
7
    i = 0
    while i < len(values):
        if values[i] % 2 == 0:
            del values[i]
        else:
            i += 1
    return values

Vi går igenom listan values och tar bort de element som vars rest vid heltalsdivision med 2 är 0, dvs. alla jämna tal. Kvar blir bara de udda talen, och vi returnerar den listan:

1
2
numbers = [1,2,3,4,5,6,7,8,9,10]
print(odd_only(numbers))

Men vad skulle hända om vi nu skrev ut numbers?

1
print(numbers)

Här har vi skapat exakt samma situation som vi gjorde med listorna list_a och list_b. Genom att ändra på värdet genom en referens (parametern values inne i odd_only) har vi också ändrat på värdet i numbers. De refererar ju till samma lista på samma plats i minnet!

Den här typen av beteende är en av de vanligaste källorna till buggar som finns, och det är orsaken till att vi fram tills nu låtsats som att alla värden är oföränderliga. Så varför tillåter vi ens detta? Det är en bra fråga, och det finns språk som faktiskt inte gör det, som t.ex. Erlang och Haskell, just eftersom det tillför en ny dimension av komplexitet och möjliga fel. För språk som tillåter föränderliga värden så handlar det dock om en och bara en sak, prestanda. Om vi måste skapa en ny lista varje gång vi gör en ändring så måste hela listan kopieras till en ny plats i minnet. Speciellt för stora datastrukturer så tar detta mycket lång tid och tar upp mycket minnesutrymme och gör vi tusentals ändringar en efter en så måste vi skapa tusentals kopior av vår data. Det finns sätt att komma runt detta, men de betraktas generellt sett som ganska esoteriska och kluriga även av andra programmerare.

Objekt och metoder

Vi såg att vi kunde ta bort element från listor med del och att vi kunde tilldela enskilda element i en lista nya värden med hjälp av subskript och tilldelning. Vi kan dock inte lägga till nya element i en lista på samma sätt. Prova t.ex. följande:

1
2
numbers = [0, 1, 2]
numbers[3] = 3

När vi försöker tilldela “nästa” index efter det sista i listan får vi ett fel. Istället måste vi använda oss av slice på ett sätt som lätt blir väldigt förvirrande:

1
2
numbers = [0, 1, 2]
numbers[3:] = [3]

Vi kan också skjuta in ett värde på en viss plats genom att skriva

1
2
numbers = [0, 1, 2]
numbers[2:2] = [1.5]

eller skriva över flera värden samtidigt genom att skriva

1
2
numbers = [0, 1, 2]
numbers[1:3] = [numbers[1] + numbers[2]]

Det här sättet att använda subskriptnotationen ser vi sällan i verkligheten utan istället använder vi metoder för att lägga till värden i listor.

Referenser pekar på objekt, som innehåller både ett
värde och information om värdets typ och attribut.

Vi har sett att variabler inte är namngivna lådor som innehåller värden, utan snarare namngivna lådor som innehåller referenser till värden. Nu är det dags att röra till det ytterligare genom att fundera över vad ett “värde” egentligen är. Vi vet ju att ett värde har en typ och att t.ex. heltal och strängar lagras på olika sätt i minnet och innebär att operatorer som + gör olika saker.

I många traditionella, så kallade statiskt typade, språk som C/C++, Rust, Java, etc. så finns det egentligen ingenting i det stycke minne som pekas ut av en variabel som säger vilken typ av data som ligger där. Istället måste programmet hålla reda på vilken typ av data det är genom att hålla koll på variablernas datatyp och vilka datatyper olika funktioner opererar på. Detta har sina fördelar men kan bli väldigt förvirrande till en början.

Python är som vi märkt ett språk där en variabel kan referera till vilken typ av data som helst. Vi säger att Python är dynamiskt typat. Så hur i hela fridens namn vet Pythontolken om a + b ska vara en addition eller en konkatenering? Jo, för att alla värden i Python är objekt. Dvs. i minnet ligger inte bara en serie 1:or och 0:or som representerar själva värdet, utan också information om vilken typ av värde det är och, i förlängningen, vilka typer av operationer man kan göra på det värdet.

Den här kombinationen av värde och dess datayp kallas som sagt för ett objekt och vi kan kommunicera med ett objekt genom att skicka meddelanden till det. Vi kommer prata mer om detta längre fram i kursen när vi kommer in på objektorientering, men nu räcker det med att veta att vi kan skicka meddelanden till objekt genom att anropa objektets metoder med hjälp av en referens, t.ex. en variabel, och punktnotation:

1
2
3
numbers = [0, 1, 2]
numbers.insert(3, 3)
print(numbers)

Här anropar vi metoden insert på listobjektet som variabeln numbers refererar till. Metoden tar två argument, ett index och det värde som skall stoppas in på det indexet. Här lade vi till värdet 3 på index 3. För att tydliggöra vilket argument som är vilket kan vi köra numbers.insert(0, -1) som kommer skjuta in -1 på index 0 och flytta alla andra element ett steg längre bak i listan. En nackdel med insert är att vi måste veta exakt var ett värde skall läggas till, och det blev egentligen inte mycket enklare än att använda slice. Istället kan vi använda metoden append som lägger till ett element sist i en lista.

Du kan läsa mer om listmetoderna i den officiella pythondokumentationen: More on Lists.

Det även objekt av oföränderliga datatyper, som strängar, har metoder. Skillnaden är att inga av dessa metoder ändrar på strängobjektet utan som mest kan returnera en ny sträng. Man kan läsa mer om dessa i den officiella pythondokumentationen. String Methods

Quiz

Testa din förståelse av materialet med tillhörande quiz.


Sidansvarig: Johan Falkenjack
Senast uppdaterad: 2025-09-11