Klasser#

Python er et objektorientert programmeringsspråk. Nesten alt i Python er objekter.

  • Strings er objekter

  • Lister er objekter

  • Ordbøker er objekter

  • Pakker er objekter

  • Filer er objekter

  • … og masse, masse mer

Men hva er egentlig et objekt?

Objekter#

Kort sagt kan vi si at et objekt er noe som kan

  1. inneholde data (attributter)

  2. gjøre ting (metoder)

Klasser#

For å lage et objekt må vi spesifisere hvilke data som objektet kan inneholde, og hvilke ting objektet kan gjøre.

En slik oppskrift kalles for en klasse.

Et enkelt eksempel: Teller#

La oss lage en enkel klasse i Python.

class Teller:
    # Konstruktøren
    def __init__(self):
        self.antall = 0
    
    # Metode
    def tell(self):
        self.antall += 1

t = Teller() # Oppretter et objekt av klassen "Teller" og kaller det for "t"
print("Teller:", t.antall) # Skriver ut antall
t.tell() # Teller en gang
print("Teller:", t.antall) # Skriver ut antall
Teller: 0
Teller: 1

Dette programmet lager en klasse Teller. Deretter opprettes et objekt t som er av klassen Teller. Så bruker vi tell()-metoden (som er innebygd i objektet t) for å telle oppover. Hver gang vi gjør et kall på tell()-metoden, øker antallet.

Konstruktøren og metoder#

Konstruktøren __init__() er en spesiell metode som lar oss definere hva som skal skje når et objekt opprettes. Når vi oppretter et objekt så kjøres konstruktøren. Det er vanlig å sette objekters attributter her.

Metoder er funksjoner som definerer hva et objekt kan gjøre.

Et nytt eksempel: Mario#

La oss ta et eksempel fra spill-verden. En nær og kjær folkehelt og rørlegger fra Brooklyn, New York: Mario.

    ____▒▒▒▒▒
    —-▒▒▒▒▒▒▒▒▒
    —–▓▓▓░░▓░
    —▓░▓░░░▓░░░
    —▓░▓▓░░░▓░░░
    —▓▓░░░░▓▓▓▓
    ——░░░░░░░░
    —-▓▓▒▓▓▓▒▓▓
    –▓▓▓▒▓▓▓▒▓▓▓
    ▓▓▓▓▒▒▒▒▒▓▓▓▓
    ░░▓▒░▒▒▒░▒▓░░
    ░░░▒▒▒▒▒▒▒░░░
    ░░▒▒▒▒▒▒▒▒▒░░
    —-▒▒▒ ——▒▒▒
    –▓▓▓———-▓▓▓
    ▓▓▓▓———-▓▓▓▓

Selv om det første Super Mario Bros ble skrevet i Assembly, som teknisk sett ikke er et objektorientert språk, kan vi se for oss at Mario er et objekt. Han har en posisjon, kan gå til venstre og høyre, og kan hoppe.

La oss lage en klasse som gjør at vi kan lage Mario-objekter.

class Mario:
    # Konstruktøren (constructor)
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.liten = True
    
    # Metoder
    def beveg_høyre(self):
        self.x += 1
        print("Mario har bevegd seg til høyre.")
        self.skriv_posisjon()
    
    def beveg_venstre(self):
        self.x -= 1
        print("Mario har bevegd seg til venstre.")
        self.skriv_posisjon()
    
    def hopp(self):
        self.y += 1
        print("Boing!")
        self.skriv_posisjon()

    def fall(self):
        self.y -= 1
        print("Mario faller.")
        self.skriv_posisjon()
    
    def spis_sopp(self):
        if self.liten:
            self.liten = False
            print("Mario vokser!")
        else:
            print("1000 poeng!")

    def skriv_posisjon(self):
        print("Mario er ved (", self.x, ",", self.y, ")")

mario = Mario(0, 0) # Instansierer et mario-objekt.
mario.beveg_høyre() # Beveger mario til høyre
mario.hopp() # Får mario til å hoppe
mario.spis_sopp() # Får mario til å spise en sopp
mario.fall() # Får mario til å falle
Mario har bevegd seg til høyre.
Mario er ved ( 1 , 0 )
Boing!
Mario er ved ( 1 , 1 )
Mario vokser!
Mario faller.
Mario er ved ( 1 , 0 )

Legg merke til konstruktøren i Mario-klassen. self-argumentet er nødvendig for å kunne sette data som objektet skal inneholde, men utenom self har funksjonen to argumenter, x og y. Disse gjør at vi kan (og må) definere en startposisjon når vi oppretter Mario-objekter, som du ser i koden når vi lager et Mario-objekt med Mario(0, 0).

I tillegg kan vi se at metodene beveg_høyre(), beveg_venstre(), hopp() og fall() kaller på en annen metode i samme objekt ved navn skriv_posisjon(). Det gjør at objektets posisjon skrives ut hver gang objektet flyttes på.

Eksempel: Flere objekter i en liste#

Vi ønsker ofte å lagre objekter i lister.

Farger lagres ofte digitalt som en kombinasjon av rød, grønn og blå. Hver verdi lagres i intervallet \([0,255]\). Vi kan for eksempel representere sort som (0, 0, 0), hvit som (255, 255, 255), rød som (255, 0, 0), grønn som (0, 255, 0), blå som (0, 0, 255) og gul som (255, 255, 0).

La oss lage en klasse Farge som gjør at vi kan lage Farge-objekter med forskjellige farger. Deretter kan vi lagre disse i en liste.

from random import randint

class Farge:
    def __init__(self, r, g, b):
        self.r = r # Rød verdi
        self.g = g # Grønn verdi
        self.b = b # Blå verdi

# Lager en tom liste
fargeliste = []

# Legger inn tre tilfeldige Farge-objekter i listen
for n in range(3):
    r = randint(0,255)
    g = randint(0,255)
    b = randint(0,255)
    fargeliste.append(Farge(r, g, b))

# Skriver ut objektene i listen
for x in fargeliste:
    print(x)
<__main__.Farge object at 0x0000029C89D93FB0>
<__main__.Farge object at 0x0000029C89D92C00>
<__main__.Farge object at 0x0000029C89D93FE0>

Som vi ser er det enkelt å lage flere objekter og legge de inn i en liste.

Vi ser også at det er tre objekter i listen fargeliste, men utskriften sier oss lite om hvilken farge det er.

__str__()-metoden#

Hvis vi ønsker å endre på hvordan objekter skrives ut kan vi legge til en __str__()-funksjon i klassen vår. Denne funksjonen skal returnere en string som print()-funksjonen skal skrive ut.

from random import randint

class Farge:
    def __init__(self, r, g, b):
        self.r = r # Rød verdi
        self.g = g # Grønn verdi
        self.b = b # Blå verdi

    def __str__(self):
        return f"({self.r}, {self.g}, {self.b})"

# Lager en tom liste
fargeliste = []

# Legger inn tre tilfeldige Farge-objekter i listen
for n in range(3):
    r = randint(0,255)
    g = randint(0,255)
    b = randint(0,255)
    fargeliste.append(Farge(r, g, b))

# Skriver ut objektene i listen
for x in fargeliste:
    print(x)
(60, 122, 37)
(165, 15, 30)
(15, 203, 80)

Vi ser at print()-funksjonen skriver ut dataene på måten som vi spesifiserte i __str__()-metoden!

Å sjekke typen til et objekt.#

Noen ganger ønsker vi å sjekke om et objekt har riktig type. For å gjøre det kan vi bruke type()-funksjonen.

class Elev:
    def __init__(self, navn):
        self.navn = navn
    
class Lærer:
    def __init__(self, navn):
        self.navn = navn

# En funksjon som tar inn et objekt og hilser ulikt avhengig av hvilken type objekt det er.
def hils(x):
    if type(x) == Elev:
        print("Hei, " + x.navn + ". Har du gjort leksene dine?")
    elif type(x) == Lærer:
        print("Hei, " + x.navn + ". Har du drukket kaffen din enda?")

# Lager en elev og hilser
person = Elev("Bernica")
hils(person)

# Lager en lærer og hilser
person2 = Lærer("Tobias")
hils(person2)
Hei, Bernica. Har du gjort leksene dine?
Hei, Tobias. Har du drukket kaffen din enda?

Funksjonen hils() svarer ulikt til ulike objekter ved å sjekke hvilken type objekt det er med type()-funksjonen.

Dunder-metoder#

Dunder-metoder (double-underscore methods) er metoder som har navn som starter og slutter med __. Vi har sett to eksempler allerede; __init__() og __str__(). Dunder-metoder kan utvide objekters funksjonalitet til å fungere med eksisterende operatorer og funksjoner som å printe med print(), indeksere med [], legge til med += osv…

__getitem__()-metoden#

Vi kan indeksere i et objekt med klammeparantes [] som en liste hvis vi utvider klassen med __getitem__()-metoden.

class Elevliste:
    def __init__(self, liste):
        self.liste = liste

    def __str__(self):
        string = ""
        for x in self.liste:
            string += x + " "
        return string
    
    def __getitem__(self, i):
        return self.liste[i]

elevliste = Elevliste(["Lasse", "Isak", "Nicolai", "Stian"])
print(elevliste)
print(elevliste[1])
Lasse Isak Nicolai Stian 
Isak

__len__()-metoden#

Vi kan bruke len()-funksjonen på objektet vårt hvis vi utvider klassen vår med __len__()-metoden.

class Elevliste:
    def __init__(self, liste):
        self.liste = liste

    def __str__(self):
        string = ""
        for x in self.liste:
            string += x + " "
        return string
    
    def __len__(self):
        return len(self.liste)

elevliste = Elevliste(["Linnea", "André", "Kieran", "Isak", "Nicolai"])
print(elevliste)
print(len(elevliste))
Linnea André Kieran Isak Nicolai 
5

Dunder-metoder lar oss altså spesifisere hvordan vi ønsker at objektene våre skal fungere i koden med de operatorene og funksjonene som vi kjenner til fra før av. En oversikt over flere dunder-metoder kan du finne i dokumentasjonen her.


Oppgaver#

Oppgave 1 🍄

Kopier Mario-klassen fra eksemplet over.

Det finnes en sopp i posisjonen \((2,1)\).

  1. Lag et Mario-objekt som starter i \((0,0)\).

  2. Bruk metodene for å simulere at Mario-objektet går bort, hopper, tar soppen og lander igjen.

Oppgave 2 🚗

I denne oppgaven skal vi lage en enkel klasse.

  1. Lag en klasse som heter Bil. Bil-objekter skal ha en attributt bilmerke som en string. bilmerke skal settes som argument til konstruktøren.

  2. Legg til en metode som heter tut(). Denne skal printe en tutelyd som tekst (f.eks Tut!).

  3. Lag et Bil-objekt, skriv ut bilmerket og bruk tut()-metoden til objektet.

Oppgave 3 🏫

Lag en klasse som heter Karakterliste. Den skal inneholde en attributt liste som er en liste som er tom fra starten av.

  1. Lag en metode legg_til() som legger inn en karakter i listen.

  2. Lag en metode seksere() som returnerer antall seksere i listen.

  3. Lag en metode gjennomsnitt() som returnerer gjennomsnittet av karakterene.

Opprett et objekt av klassen Karakterliste, legg inn 5 karakterer som tall, skriv ut antall seksere og gjennomsnittet ved å bruke metodene til objektet.

Bonus: Utvid klassen med en __str__()-metode som gjør at man får en hensiktsmessig utskrift når printer et Karakterliste-objekt.

Oppgave 4 💸

Lag en Python-klasse Bankkonto med kontonummer og saldo som attributter. Disse skal settes som argument til konstruktøren.

  1. Legg til metoder for innskudd og uttak fra kontoen, og skriv ut saldoen etter hver operasjon.

  2. Legg til en feilmelding når man prøver å ta ut mer penger enn man har.

  3. Lag et Bankkonto-objekt og test om metodene dine fungerer som de skal.

Oppgave 5 🅿️

Kopier inn klassen Bil fra den tidligere oppgaven.

  1. Utvid Bil-klassen med en __str__()-metode som returnerer bilmerket slik at vi kan bruke print()Bil-objekter.

Lag en ny klasse PHus som skal inneholde en liste plasser som er tom fra starten av, i tillegg skal man kunne legge inn en maksgrense for antall biler som argument til konstruktøren.

  1. Lag en metode parker() som legger en bil inn i plasser-listen, men dersom antall biler er over maksgrensen skal det printes at det ikke er plass og at bilen ikke ble lagt til.

  2. Utvid klassen PHus med en __str__()-metode som skriver ut bilmerkene til alle bilene som er parkert.

  3. Lag et PHus-objekt og legg inn Bil-objekter helt til maksgrensen er nådd. Skriv ut PHus-objektet til slutt.

Oppgave 6 📦

Lag en klasse Varelager som inneholder en liste over varer. Listen skal være tom når objektet dannes.

Varene skal være ordbøker med nøklene nanv, pris og antall_på_lager.

Legg til metoder for å legge til varer i lageret, fjerne varer fra lageret og skrive ut en liste over tilgjengelige varer.

Oppgave 7 ➗ (utfordring)

I denne oppgaven skal du lage en klasse Brøk. Her er noen krav:

  • Klassen skal ha attributtene teller og nevner som settes som argument til konstruktøren.

  • Den skal ha en __str__()-metode som skriver ut {teller}/{nevner}.

  • Den skal ha en metode forkort som forkorter brøken hvis det er mulig.

  • Den skal ha __mul__()- og __div__()-metoder for å håndtere multiplikasjon og divisjon med heltall og med andre Brøk-objekter.

  • Den skal ha en __pow__()-metode for å håndtere at brøken blir opphøyd i et heltall (både positivt og negativt)

  • Den skal ha __sub__()- og __add__()-metoder for å håndtere addisjon og subtraksjon med heltall og andre Brøk-objekter.