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 0x0000017AA1C6B7A0>
<__main__.Farge object at 0x0000017AA09F7740>
<__main__.Farge object at 0x0000017AA1CB2AE0>

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__()- og __repr__()-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)
(244, 231, 206)
(46, 138, 163)
(232, 69, 15)

Metoden __str__ fungerer bare dersom vi kjører print på et objekt direkte, hvis vi printer det som en del av en liste, ser vi fortsatt disse minne-greiene.

print(fargeliste)
[<__main__.Farge object at 0x0000017AA1C6B7A0>, <__main__.Farge object at 0x0000017AA09F7740>, <__main__.Farge object at 0x0000017AA1CB0440>]

For å få objekter til å vises på denne måten kan vi bruke __repr__()-metoden i stedet.

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 __repr__(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))

print(fargeliste)
[(55, 7, 255), (54, 83, 67), (94, 236, 168)]

Å 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("Theodor")
hils(person)

# Lager en lærer og hilser
person2 = Lærer("Tobias")
hils(person2)
Hei, Theodor. 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.

Flere dunder-metoder#

Dunder-metoder (double-underscore methods) er metoder som har navn som starter og slutter med __. Vi har sett noen eksempler allerede; __init__() og __str__(). Dunder-metoder kan utvide objekters funksjonalitet til å fungere med eksisterende operatorer og funksjoner som å printe med print(), finne lengden med len(), addere med +, subtrahere med - osv…

La oss lage en klasse Skoleklasse, og legge til muligheten for å legge sammen flere klasser med +-operatoren.

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

    def __str__(self):
        # Gir en string når man kjører print(Skoleklasse())
        return f"Klasse: {self.liste}"
    
    def __add__(self, other):
        # Gir en ny skoleklasse som er en sammensetning av self og other
        return Skoleklasse(self.liste + other.liste)
    
    
klasse1 = Skoleklasse(["Lasse", "Isak"])
klasse2 = Skoleklasse(["Nicolai", "Stian"])
print(klasse1)
print(klasse2)
print(klasse1 + klasse2)
Klasse: ['Lasse', 'Isak']
Klasse: ['Nicolai', 'Stian']
Klasse: ['Lasse', 'Isak', 'Nicolai', 'Stian']

Her er noen operatorer du kan få bruk for. De fungerer på samme måte som __add__. Man kan returnere et nytt objekt som er en sammensetning av self og other. Argumentet other er det objektet som står til høyre for operatoren, mens self er objektet til venstre.

Operator

Dunder-metode

+

__add__

-

__sub__

*

__mul__

/

__truediv__

**

__pow__

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 navn, 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 __mul__()- og __truediv__()-metoder for å håndtere multiplikasjon og divisjon med heltall og med andre Brøk-objekter.

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

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

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