Osa 9

Oliot attribuuttina

Aikaisemmin nähtiin esimerkkejä luokista, joissa attribuutteina oli käytetty esimerkiksi listoja. Samalla tavalla myös omista luokista luotuja olioita voi käyttää toisten olioiden attribuutteina. Seuraavissa esimerkeissä on määritelty luokat Kurssi, Opiskelija ja Opintosuoritus. Opintosuorituksessa hyödynnetään kahta ensimmäistä luokkaa. Luokkien sisäinen toteutus on lyhyt, jotta esimerkki toisi esille oleellisen.

Esimerkissä jokainen luokka on kirjoitettu omaan tiedostoonsa.

Esitellään aluksi luokka Kurssi, joka on määritelty tiedostossa kurssi.py:

class Kurssi:
    def __init__(self, nimi: str, koodi: str, opintopisteet: int):
        self.nimi = nimi
        self.koodi = koodi
        self.opintopisteet = opintopisteet

Luokka Opiskelija mallintaa yhtä opiskelijaa. Luokka on määritelty tiedostossa opiskelija.py:

class Opiskelija:
    def __init__(self, nimi: str, opiskelijanumero: str, opintopisteet: int):
        self.nimi = nimi
        self.opiskelijanumero = opiskelijanumero
        self.opintopisteet = opintopisteet

Luokka Opintosuoritus hyödyntää luokkia Kurssi ja Opiskelija suorituksen tallentamiseen. Huomaa, että luokat tuodaan mukaan import-lauseella:

from kurssi import Kurssi
from opiskelija import Opiskelija

class Opintosuoritus:
    def __init__(self, opiskelija: Opiskelija, kurssi: Kurssi, arvosana: int):
        self.opiskelija = opiskelija
        self.kurssi = kurssi
        self.arvosana = arvosana

Esimerkki opintosuoritusten lisäämisestä listaan:

from opintosuoritus import Opintosuoritus
from kurssi import Kurssi
from opiskelija import Opiskelija

# Luodaan lista opiskelijoista
opiskelijat = []
opiskelijat.append(Opiskelija("Olli", "1234", 10))
opiskelijat.append(Opiskelija("Pekka", "3210", 23))
opiskelijat.append(Opiskelija("Leena", "9999", 43))
opiskelijat.append(Opiskelija("Tiina", "3333", 8))

# Kurssi Ohjelmoinnin perusteet
ohpe = Kurssi("Ohjelmoinnin perusteet", "ohpe1", 5)

# Annetaan suoritukset kaikille opiskelijoille, kaikille arvosanaksi 3
suoritukset = []
for opiskelija in opiskelijat:
    suoritukset.append(Opintosuoritus(opiskelija, ohpe, 3))

# Tulostetaan kaikista suorituksista opiskelijan nimi
for suoritus in suoritukset:
    print(suoritus.opiskelija.nimi)
Esimerkkitulostus

Olli Pekka Leena Tiina

Tarkastellaan lähemmin riviä print(suoritus.opiskelija.nimi):

  • suoritus on luokan Opintosuoritus mukainen olio
  • Niinpä muuttuja opiskelija viittaa suoritukseen tallennettuun Opiskelija-olioon
  • Opiskelija-luokan muuttuja nimi sisältää opiskelijan nimen

Milloin import tarvitaan?

Edellisessä esimerkissä käytetään muutamassa kohdassa import:ia:

from opintosuoritus import Opintosuoritus
from kurssi import Kurssi
from opiskelija import Opiskelija

# koodi

Importia tarvitaan vain jos tiedostossa käytetään jossain muualla määriteltyä koodia. Näin on esimerkiksi kun käytetään jotain Pythonin valmista kalustoa, esim. matemaattisia operaatiota tarjoavaa moduulia math:

import math

x = 10
print(f"luvun {x} {neliöjuuri math.sqrt(x)}")

Edellisessä tehtävässä oletettiin, että luokat on määritelty omissa tiedostoissaan. Esimerkki toteaa mm. Esitellään aluksi luokka Kurssi, joka on määritelty tiedostossa kurssi.py ja importin tarve siis johtuu tästä.

Jos kaikki koodi sijoitetaan samaan tiedostoon, kuten kaikissa kurssin ohjelmointitehtävissä ohjeistetaan, et tarvitse import:ia luomiesi luokkien käytöön.

Jos siis päädyt kirjottamaan kurssilla seuraavanlaista koodia

from henkilo import Henkilo

# koodi

ratkaisusi on todennäköisesti väärä! Lisää importin käytöstä osan 7 materiaalissa.

Loading

Olion attribuuttina lista olioita

Äskeisissä esimerkeissä oliolla oli attribuuttina yksittäinen toisen luokan olio, esim. henkilöllä attribuuttina lemmikki ja opintosuorituksella attribuuttina kurssi.

Olio-ohjelmoinnissa törmätään kutenkin usein tilanteeseen, jossa oliolla on attribuuttina joukko toisen luokan oliota. Eräs tälläinen tilanne kuvaa joukkueen ja sen pelaajien välistä yhteyttä:

class Pelaaja:
    def __init__(self, nimi: str, maalit: int):
        self.nimi = nimi
        self.maalit = maalit

    def __str__(self):
        return f"{self.nimi} (maaleja {self.maalit})"

class Joukkue:
    def __init__(self, nimi: str):
        self.nimi = nimi
        self.pelaajat = []

    def lisaa_pelaaja(self, pelaaja: Pelaaja):
        self.pelaajat.append(pelaaja)

    def yhteenveto(self):
        maalit = []
        for pelaaja in self.pelaajat:
            maalit.append(pelaaja.maalit)
        print("Joukkue", self.nimi)
        print("Pelaajia", len(self.pelaajat))
        print("Pelaajien maalimäärät", maalit)

Käyttöesimerkki:

kupa = Joukkue("Kumpulan pallo")
kupa.lisaa_pelaaja(Pelaaja("Erkki", 10))
kupa.lisaa_pelaaja(Pelaaja("Emilia", 22))
kupa.lisaa_pelaaja(Pelaaja("Antti", 1))
kupa.yhteenveto()
Esimerkkitulostus

Joukkue Kumpulan pallo Pelaajia 3 Pelaajien maalimäärät [10, 22, 1]

Loading

None eli viite ei mihinkään

Pythonissa muuttujat viittaavat aina johonkin olioon. On kuitenkin tilanteita, joissa haluaisimme määrittää arvon, joka ei viittaa mihinkään. Arvoa None käytetään esittämään tyhjää viittausta.

Jos esimerkiksi luokkaan joukkue lisättäisiin metodi, joka etsii joukkueen pelaajan, saattaisi olla luontevaa esittää paluuarvolla None tilanne, jossa pelaajaa ei löydy:

class Pelaaja:
    def __init__(self, nimi: str, maalit: int):
        self.nimi = nimi
        self.maalit = maalit

    def __str__(self):
        return f"{self.nimi} (maaleja {self.maalit})"

class Joukkue:
    def __init__(self, nimi: str):
        self.nimi = nimi
        self.pelaajat = []

    def lisaa_pelaaja(self, pelaaja: Pelaaja):
        self.pelaajat.append(pelaaja)

    def etsi(self, nimi: str):
        for pelaaja in self.pelaajat:
            if pelaaja.nimi == nimi:
                return pelaaja
        return None

Käyttöesimerkki:

kupa = Joukkue("Kumpulan pallo")
kupa.lisaa_pelaaja(Pelaaja("Erkki", 10))
kupa.lisaa_pelaaja(Pelaaja("Emilia", 22))
kupa.lisaa_pelaaja(Pelaaja("Antti", 1))

pelaaja1 = kupa.etsi("Antti")
print(pelaaja1)
pelaaja2 = kupa.etsi("Jukkis")
print(pelaaja2)
Esimerkkitulostus

Antti (maaleja 1) None

None-arvojen kanssa pitää olla tarkkana. On hyvin tyypillistä, että ohjelmassa kutsutaan jotain metodia oliolle (tai pyydetään attribuutin arvoa oliolta), joka onkin None:

kupa = Joukkue("Kumpulan pallo")
kupa.lisaa_pelaaja(Pelaaja("Erkki", 10))

pelaaja = kupa.etsi("Jukkis")
print(f"Jukkiksen maalimäärä {pelaaja.maalit}")

Jos näin tehdään, ohjelma päättyy virheeseen:

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

None-arvojen varalta onkin syytä tehdä tarkistus, ennen kuin riskialtista koodia kutsutaan:

kupa = Joukkue("Kumpulan pallo")
kupa.lisaa_pelaaja(Pelaaja("Erkki", 10))

pelaaja = kupa.etsi("Jukkis")
if pelaaja is not None:
    print(f"Jukkiksen maalimäärä {p.maalit}")
else:
    print(f"Jukkis ei pelaa Kumpulan pallossa :(")
Esimerkkitulostus

Jukkis ei pelaa Kumpulan pallossa :(

Loading
Seuraava osa: