Osa 9

Objekt som attribut

Vi har redan sett exempel på klasser som har listor som attribut. Eftersom det alltså inte finns något som hindrar oss från att inkludera mutabla objekt som attribut i våra klasser, kan vi lika gärna använda instanser av våra egna klasser som attribut i andra klasser som vi själva har definierat. I följande exempel kommer vi att definiera klasserna Kurs, Studerande och SlutfordKurs. En slutförd kurs använder sig av de två första klasserna. Klassdefinitionerna är mycket korta och enkla för att vi bättre ska kunna koncentrera oss på tekniken att använda instanser av våra egna klasser som attribut.

Vi kommer att anta att varje klass definieras i en separat fil.

Först definierar vi klassen Kurs i en fil med namnet kurs.py:

class Kurs:
    def __init__(self, namn: str, kod: str, studiepoang: int):
        self.namn = namn
        self.kod = kod
        self.studiepoang = studiepoang

Till näst, klassen Studerande i en fil med namnet studerande.py:

class Studerande:
    def __init__(self, namn: str, studerandenummer: str, studiepoang: int):
        self.namn = namn
        self.studerandenummer = studerandenummer
        self.studiepoang = studiepoang

Slutligen är klassen SlutfordKurs definierad i en fil med namned SlutfordKurs.py. Eftersom den använder de två andra klasserna, måste de importeras innan de kan användas:

from kurs import Kurs
from studerande import Studerande

class SlutfordKurs:
    def __init__(self, studerande: Studerande, kurs: Kurs, vitsord: int):
        self.studerande = studerande
        self.kurs = kurs
        self.vitsord = vitsord

Här är ett exempel av en huvudfunktion som lägger till några slutförda kurser i en lista:

from slutfordkurs import SlutfordKurs
from kurs import Kurs
from studerande import Studerande

# Vi skapar en lista av studeranden
studeranden = []
studeranden.append(Studerande("Olle", "1234", 10))
studeranden.append(Studerande("Peter", "3210", 23))
studeranden.append(Studerande("Lena", "9999", 43))
studeranden.append(Studerande("Tina", "3333", 8))

# Kurssen Introduktion till Programmering
itp = Kurs("Introduktion till Programmering", "itp1", 5)

# Vi ger prestationer för varje student, med vitsordet 3 till alla
prestationer = []
for studerande in studeranden:
    prestationer.append(SlutfordKurs(studerande, itp, 3))

# Vi skriver ut studerandenas namn för varje avklarad kurs
for prestation in prestationer:
    print(prestation.studerande.namn)
Exempelutskrift

Olle Peter Lena Tina

Vad exakt händer med alla prickar på raden print(kurs.studerande.namn)?

  • kurs är en instans av klassen SlutfordKurs
  • studerande refererar till ett attribut i objektet SlutfordKurs, som är ett objekt av typen Studerande
  • attributnamnet i Studerande-objektet innehåller namnet på studenten

När är en import nödvändig?

I exemplen ovan förekommer en import-sats ganska många gånger:

from slutfordkurs import SlutfordKurs
from kurs import Kurs
from studerande import Studerande

# kod

En importsats är bara nödvändig när man använder kod som är definierad någonstans utanför den aktuella filen (eller Python-tolksessionen). Detta inkluderar situationer där vi vill använda något som är definierat i Pythons standardbibliotek. Modulen math innehåller till exempel vissa matematiska operationer:

import math

x = 10
print(f"kvadratroten av {x} är {math.sqrt(x)}")

I exemplet ovan antog vi att de tre klasserna definierades i var sin fil och att huvudfunktionen kördes från ytterligare en fil. Det var därför import-satserna var nödvändiga.

Om all programkod skrivs i samma fil, vilket de flesta övningarna i den här kursen rekommenderar, behöver du inte import-satser för att använda de klasser du har definierat.

Ifall du märker dig själv skriva något i stil med

from person import Person

# kod

är det sannolikt att du förstått nånting felaktigt. Ifall du behöver en uppfriskare så introducerades import deklarationen för första gången i modul 7 av kursmaterialet.

Loading

En lista med objekt som attribut till ett objekt

I exemplen ovan använde vi enstaka instanser av andra klasser som attribut: en Person har ett enda Husdjur som attribut, och en SlutfordKurs har en Studerande och en Kurs som attribut.

I objektorienterad programmering är det ofta så att vi vill ha en samling objekt som attribut. Till exempel följer relationen mellan ett idrottslag och dess spelare detta mönster:

class Spelare:
    def __init__(self, namn: str, mal: int):
        self.namn = namn
        self.mal = mal

    def __str__(self):
        return f"{self.namn} (mål {self.mal})"

class Lag:
    def __init__(self, namn: str):
        self.namn = namn
        self.spelare = []

    def tillsatt_spelare(self, spelare: Spelare):
        self.spelare.append(spelare)

    def sammanfattning(self):
        mal = []
        for spelare in self.spelare:
            mal.append(spelare.mal)
        print("Lag:", self.namn)
        print("Spelare:", len(self.spelare))
        print("Spelarnas målmängd:", mal)

Ett exempel på hur vår klass fungerar:

gumboll = Lag("Gumtäkts boll")
gumboll.tillsatt_spelare(Spelare("Erik", 10))
gumboll.tillsatt_spelare(Spelare("Emilia", 22))
gumboll.tillsatt_spelare(Spelare("Anton", 1))
gumboll.sammanfattning()
Exempelutskrift

Lag: Gumtäkts boll Spelare: 3 Spelarnas målmängd: [10, 22, 1]

Loading

None: en referens till ingenting

I Python-programmering refererar alla initialiserade variabler till ett objekt. Det finns dock oundvikligen situationer där vi måste referera till något som inte existerar, utan att orsaka fel. Nyckelordet None representerar just ett sådant "tomt" objekt.

Låt oss fortsätta från exemplet med lag och spelare ovan och anta att vi vill lägga till en metod för att söka efter spelare i laget med hjälp av spelarens namn. Om ingen sådan spelare hittas kan det vara vettigt att returnera None:

class Spelare:
    def __init__(self, namn: str, mal: int):
        self.namn = namn
        self.mal = mal

    def __str__(self):
        return f"{self.namn} (mål {self.mal})"

class Lag:
    def __init__(self, namn: str):
        self.namn = namn
        self.spelare = []

    def tillsatt_spelare(self, spelare: Spelare):
        self.spelare.append(spelare)

    def sok(self, namn: str):
        for spelare in self.spelare:
            if spelare.namn == namn:
                return spelare
        return None

Låt oss testa vår funktion:

gumboll = Lag("Gumtäkts boll")
gumboll.tillsatt_spelare(Spelare("Erik", 10))
gumboll.tillsatt_spelare(Spelare("Emilia", 22))
gumboll.tillsatt_spelare(Spelare("Anton", 1))

spelare1 = gumboll.sok("Anton")
print(spelare1)
spelare2 = gumboll.sok("Johan")
print(spelare2)
Exempelutskrift

Anton (mål 1) None

Var dock försiktig med None. Det kan ibland orsaka mer problem än det löser. Det är ett vanligt programmeringsfel att försöka komma åt en metod eller ett attribut via en referens som utvärderas till None:

gumboll = Lag("Gumtäkts boll")
gumboll.tillsatt_spelare(Spelare("Erik", 10))

spelare = gumboll.sok("Johan")
print(f"Johans målmängd {spelare.mal}")

Exekverande av ovanstående skulle orsaka ett fel:

Exempelutskrift
Traceback (most recent call last): File "", line 1, in AttributeError: 'NoneType' object has no attribute 'mal'

Det är en god idé att kontrollera om det finns None innan du försöker komma åt några attribut eller metoder för returvärden:

gumboll = Lag("Gumtäkts boll")
gumboll.tillsatt_spelare(Spelare("Erik", 10))

spelare = gumboll.sok("Johan")
if spelare is not None:
    print(f"Johans målmängd {p.mal}")
else:
    print(f"Johan spelar inte i Gumtäkts boll :(")
Exempelutskrift

Johan spelar inte i Gumtäkts boll :(

Loading
Du har nått slutet av den här delen! Fortsätt till nästa del:

Se dina poäng genom att klicka på cirkeln nere till höger av sidan.