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)
Olli Pekka Leena Tiina
Tarkastellaan lähemmin riviä print(suoritus.opiskelija.nimi)
:
suoritus
on luokanOpintosuoritus
mukainen olio- Niinpä muuttuja
opiskelija
viittaa suoritukseen tallennettuunOpiskelija
-olioon Opiskelija
-luokan muuttujanimi
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.
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()
Joukkue Kumpulan pallo Pelaajia 3 Pelaajien maalimäärät [10, 22, 1]
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)
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:
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 :(")
Jukkis ei pelaa Kumpulan pallossa :(