Luokan määrittely ja olioiden käsittely
Jos haluamme tehdä ohjelman, joka käsittelee opiskelijaolioita, on
ohjelmassa jotenkin määriteltävä se, millaisia opiskelijaoliot ovat ja
mitä toimenpiteitä niille voi tehdä. Tämä tehdään luokassa, jonka nimi
on Opiskelija
. Kun on kirjoitettu Opiskelija
-luokka, voidaan
luoda Opiskelija
-olioita ja suorittaa niille erilaisia
toimenpiteitä.
Yleisesti siis luokassa (engl. class) määritellään, millaisia ominaisuuksia sen luokan olioilla (engl. object) on ja mitä toimenpiteitä luokan olioille voi tehdä. Luokkaa voidaan tavallaan verrata jonkin koneen piirustuksiin ja olioita piirustusten perusteella rakennettuihin koneisiin. Samoin kuin yksien piirustusten perusteella voidaan rakentaa monta konetta, yhdestä luokasta voidaan luoda monta oliota.
Tarkastelemme opiskelijaesimerkin avulla, miten luokka kirjoitetaan ja
miten luokan olioita voidaan luoda ja käyttää. Kenttien avulla
kerrotaan, mitä ominaisuuksia luokan olioilla on ja mikä on jonkin
ominaisuuden arvo tietyllä oliolla. Esimerkiksi tässä määriteltävällä
Opiskelija
-oliolla on kentät __nimi
, __opiskelijanumero
,
__tenttiarvosana
ja __harjoitusarvosana
, joiden arvoksi voidaan
tallentaa käsiteltävän Opiskelija
-olion nimi, opiskelijanumero sekä
tentti- ja harjoitusarvosana vastaavasti. Jokaisella luotavalla oliolla
on omat kenttien arvot, jotka eivät riipu toisten saman luokan olioiden
kenttien arvoista. Olion kenttien arvot säilyvät niin kauan kuin olio on
olemassa (ne eivät siis häviä esimerkiksi jostain funktioista
poistuttaessa), ellei ohjelmassa erikseen muuteta jonkin kentän arvoa.
Edellä luetellut kenttien nimet alkavat kahdella alaviivamerkillä
(__
). Syy tähän kerrotaan vähän myöhemmin.
Olion kenttiä voi käyttää luokan sisällä samaan tapaan kuin tavallisia
muuttujia. Kentän nimen edessä kuitenkin kerrotaan aina, minkä olion
kentästä on kysymys. Tämä tehdään pistenotaation avulla. Jos halutaan
käsitellä muuttujan oliomuuttuja
viittaman olion kenttää kentta
,
kirjoitetaan oliomuuttuja.kentta
. Usein luokan sisällä olevissa
metodeissa käsitellään sitä oliota, jota ollaan juuri luomassa tai jolle
on juuri kutsuttu luokan jotain metodia. Tällaiseen olioon viitataan
nimellä self
. Tätä asiaa selvennetään lisää, kun käydään läpi luokan
Opiskelija
sisältöä.
Luokan määrittely aloitetaan otsikolla, johon kuuluu sana class
ja
luokan nimi. Sen jälkeen tulee kaksoispiste. Yleinen tapa on aloittaa
luokan nimi isolla kirjaimella.
class Opiskelija:
Seuraavien rivien sisennysten avulla osoitetaan, mitkä rivit kuuluvat tämän luokan määrittelyyn. Luokan määritettelyssä kerrotaan, millaisia toimenpiteitä luokan olioille voidaan suorittaa. Tämä määritellään metodien avulla. Samaan tapaan kuin funktio, metodi on kappale koodia, jolle on annettu oma nimi. Metodi liittyy kuitenkin aina johonkin luokkaan ja sitä kutsutaan aina jollekin tämän luokan oliolle. Metodia kutsutaan vähän eri tavalla kuin funktioita. Aikaisemmin on ollut jo esimerkkejä siitä, kuinka Pythonin valmiita metodeita on kutsuttu esimerkiksi listoja, merkkijonoja ja tiedostoja käsiteltäessä. Nyt olioiden yhteydessä tutustumme siihen, miten metodeita voi kirjoittaa itse.
Luokan määrittelyssä kerrotaan yleensä aluksi, mitä tehdään, kun luodaan
uusi tämän luokan olio. Ohjelmoijan on kerrottava esimerkiksi se,
millaisia alkuarvoja annetaan olion kentille. Tämä tehdään metodissa,
jonka nimi on __init__
(Nimen alussa ja lopussa on kaksi
alaviivamerkkiä _
.) Kaikilla metodeilla on ensimmäinen parametri,
jonka nimi on self
. Tämä tarkoittaa sitä oliota, jota ollaan juuri
luomassa tai käsittelemässä. Tämän lisäksi metodilla voi olla muita
parametreja, joiden avulla metodille annetaan tietoa käytettävistä
arvoista. Esimerkiksi metodin __init__
parametrien avulla kerrotaan
usein, mitä alkuarvoja luotavan olion kentille annetaan.
Kirjoitetaan luokan Opiskelija
metodi __init__
siten, että
luotavalle Opiskelija
-oliolle annettava nimi ja opiskelijanumero
annetaan metodin parametrina. Uuden opiskelijan tentti- ja
harjoitusarvosanoiksi asetetaan aluksi 0
.
def __init__(self, annettu_nimi, numero):
self.__nimi = annettu_nimi
self.__opiskelijanumero = numero
self.__tenttiarvosana = 0
self.__harjoitusarvosana = 0
Tässä siis ilmaus self.kentan_nimi
tarkoittaa sen
Opiskelija
-olion kenttää, jota ollaan juuri luomassa. Kun
Opiskelija-luokkaan on kirjoitettu tällainen __init__
-metodi,
voidaan uusia Opiskelija
-olioita luoda seuraavaan tapaan (yleensä
luominen tapahtuu luokan ulkopuolella, esimerkiksi pääohjelmassa):
kurssilainen1 = Opiskelija("Mia Lahti", "608012")
Tässä kurssilainen1
on muuttuja, joka pannaan sijoituskäskyllä
viittaamaan luotavaan olioon. Sen kautta on myöhemmin mahdollisuus
päästä käsittelemään nyt luotavaa oliota. Sijoituskäskyn oikealla
puolella oleva osa saa aikaan varsinaisen olion luonnin. Python-tulkki
luo uuden olion ja suorittaa sitten __init__
-metodissa olevat käskyt
niin, että parametrin annettu_nimi
arvo on merkkijono
"Mia Lahti"
ja parametrin numero
arvo merkkijono "608012"
.
Metodin __init__
ensimmäiselle parametrille self
ei anneta
oliota luodessa arvoa, vaan parametrilla tarkoitetaan sitä oliota, jota
ollaan juuri luomassa.
Tarkastellaan sitten luokan muita metodeja. Jotta luodun
Opiskelija
-olion tentti- ja harjoitusarvosanoja voisi myöhemmin
muuttaa, määritellään metodit tentti- ja harjoitusarvosanojen
muuttamista varten. Alla on kirjoitettu metodi, jonka avulla voi muuttaa
olion __tenttiarvosana
-kenttää. Metodi saa parametrina opiskelijalle
annettavan uuden tenttiarvosanan. Ennen kentän arvon muuttamista metodi
tarkistaa, että parametri on sallitulla välillä 0–5. Jos parametri ei
ole tällä välillä, metodi ei tee mitään. Silloin käsiteltävän olion
vanha __tenttiarvosana
-kentän arvo jää voimaan.
def muuta_tenttiarvosana(self, arvosana):
if 0 <= arvosana <= 5:
self.__tenttiarvosana = arvosana
Metodin ensimmäinen parametri self
tarkoittaa sitä
Opiskelija
-oliota, jolle metodia ollaan kutsumassa. Jos on
aikaisemmin luotu Opiskelija
-olio ja pantu muuttuja
kurssilainen1
viittaamaan siihen, voidaan metodia kutsua
seuraavasti:
kurssilainen1.muuta_tenttiarvosana(4)
Usein ohjelmassa on luotu useita saman luokan olioita. Voitaisiin
esimerkiksi luoda toinen Opiskelija
-olio ja panna muuttuja
kurssilainen2
viittaamaan siihen kirjoittamalla
kurssilainen2 = Opiskelija("Niko Virtanen", "588123")
Silloin tämän jälkimmäisen opiskelijan tenttiarvosanaa voitaisiin muuttaa kirjoittamalla
kurssilainen2.muuta_tenttiarvosana(3)
Metodin kutsussa ennen pistettä oleva muuttuja määrää sen, mille oliolle
metodia kutsutaan. Aikaisemmin esitetty ensimmäinen
muuta_tenttiarvosana
-kutsu muutti Mia Lahden tenttiarvosanaa, kun taas
yllä esitetty kutsu muuttaa Niko Virtasen tenttiarvosanaa. Jokaisella
oliolla on omat kopionsa kenttien arvoista ja muutokset yhden olion
kentän arvossa eivät mitenkään vaikuta toisten saman luokan olioiden
kenttien arvoihin.
Kannattaa huomata, että metodien kutsuissa ei ole lainkaan sulkujen
sisällä annettu käsiteltävää oliota eli parametrin self
arvoa. Se on
annettu kutsussa jo ennen pistettä ja metodin nimeä. Sen sijaan metodin
muiden parametrien arvot on annettu ihan samalla tavalla kuin
funktioillakin.
Seuraava animaatio vielä esittää olioiden luomista ja niiden metodien
toimintaa. Animaatiossa esitetyssä koodissa on käytetty opiskelijoilla
lyhyempiä nimiä ja opiskelijanumeroita,
jotta oliot mahtuisivat paremmin näytölle. Samasta syystä myös
metodin muuta_tenttiarvosana
nimi on muutettu muotoon
muuta_tentti
. Animaatiossa on vain
osa Opiskelija
-luokan koodista.
Metodi muuta_harjoitusarvosana
kirjoitetaan samalla tavalla
kuin muuta_tenttiarvosana
.
Ainoastaan muutettavan kentän nimi on eri.
Kirjoitetaan sitten luokkaan metodi, jonka avulla voidaan laskea opiskelijan kokonaisarvosana. Se lasketaan tentti- ja harjoitusarvosanojen keskiarvona. Aluksi kuitenkin tarkistetaan, onko toisen tai molempien osasuoritusten arvosana 0. Siinä tapauksessa kokonaisarvosanaksi tulee 0 keskiarvosta riippumatta. Metodi palauttaa arvonaan lasketun keskiarvon. Metodin paluuarvoa voidaan käyttää hyväksi aivan samalla tavalla kuin funktioiden paluuarvoja.
def laske_kokonaisarvosana(self):
if self.__tenttiarvosana == 0 or self.__harjoitusarvosana == 0:
arvosana = 0
else:
arvosana = (self.__tenttiarvosana +
self.__harjoitusarvosana + 1) // 2
return arvosana
Keskiarvon laskemisessa lisätään osasuoritusarvosanojen summaan ykkönen. Näin saadaan aikaan se, että puolet numerot pyöristyvät ylöspäin. Laskemisessa suoritetaan kokonaislukujen jakolasku, jolloin saatu arvosana on aina kokonaisluku.
Alla on esimerkki metodin kutsumisesta ja sen paluuarvon tulostamisesta. (Oletetaan jälleen, että ohjelmassa on luotu aikaisemmin mainitut oliot.)
print("Nikon kurssiarvosana on", kurssilainen2.laske_kokonaisarvosana())
Mainittujen metodien lisäksi luokkaan määritellään metodeita, joiden
avulla voi luokan ulkopuolelta selvittää jonkin kentän arvon. Nämä
metodit siis vain palauttavat yhden kentän arvon eivätkä tee mitään
muuta. Alla esimerkkinä metodit kerro_nimi
ja
kerro_tenttiarvosana
. Myös kahdelle muulle kentälle määritellään
vastaavat metodit.
def kerro_nimi(self):
return self.__nimi
def kerro_tenttiarvosana(self):
return self.__tenttiarvosana
Seuraavaksi koko luokan määrittely yhtenä kokonaisuutena. Määrittelyyn on lisätty myös kommentit.
# Luokka Opiskelija kuvaa eraan ohjelmointikurssin
# yhta opiskelijaa.
class Opiskelija:
# Metodi __init__ antaa alkuarvot olion kentille.
# Luotavalle opiskelijalle annettava nimi ja opiskelijanumero
# annetaan metodin parametreina.
def __init__(self, annettu_nimi, numero):
self.__nimi = annettu_nimi
self.__opiskelijanumero = numero
self.__tenttiarvosana = 0
self.__harjoitusarvosana = 0
# Metodi palauttaa opiskelijan nimen.
def kerro_nimi(self):
return self.__nimi
# Metodi palauttaa opiskelijan opiskelijanumeron.
def kerro_opiskelijanumero(self):
return self.__opiskelijanumero
# Metodi palauttaa opiskelijan tenttiarvosanan.
def kerro_tenttiarvosana(self):
return self.__tenttiarvosana
# Metodi palauttaa opiskelijan harjoitusarvosanan.
def kerro_harjoitusarvosana(self):
return self.__harjoitusarvosana
# Metodi muuttaa opiskelijan tenttiarvosanan. Uusi arvosana
# annetaan metodin parametrina.
def muuta_tenttiarvosana(self, arvosana):
if 0 <= arvosana <= 5:
self.__tenttiarvosana = arvosana
# Metodi muuttaa opiskelijan harjoitusarvosanan. Uusi arvosana
# annetaan metodin parametrina.
def muuta_harjoitusarvosana(self, arvosana):
if 0 <= arvosana <= 5:
self.__harjoitusarvosana = arvosana
# Metodi laskee ja palauttaa opiskelijan kokonaisarvosanan.
def laske_kokonaisarvosana(self):
if self.__tenttiarvosana == 0 or self.__harjoitusarvosana == 0:
arvosana = 0
else:
arvosana = (self.__tenttiarvosana +
self.__harjoitusarvosana + 1) // 2
return arvosana
Seuraava pääohjelma luo kaksi Opiskelija
-oliota niin, että luotavien
olioiden tiedot kysytään käyttäjältä. Ohjelma laskee opiskelijoiden
kokonaisarvosanat ja tulostaa ne sekä opiskelijoiden muut tiedot.
Arvosanojen lukemiseen käyttäjältä on määritelty oma apufunktio. Näin
mahdollisten virheellisten syötteiden käsittely voidaan hoitaa helposti.
Ohjelmaa kirjoittaessa on oletettu, että pääohjelma on samassa
tiedostossa luokan Opiskelija
kanssa. Jos pääohjelma on kirjoitettu
toiseen tiedostoon (kuten usein käytännössä tehdään), pitää siihen tehdä
pieniä lisäyksiä, joista kerrotaan tarkemmin vähän myöhemmin.
def lue_kokonaisluku():
luku_onnistui = False
while not luku_onnistui:
try:
luku = int(input())
luku_onnistui = True
except ValueError:
print("Virheellinen kokonaisluku!")
print("Anna uusi!")
return luku
def main():
nimi1 = input("Anna 1. opiskelijan nimi: ")
op_nro1 = input("Anna 1. opiskelijan opiskelijanumero: ")
kurssilainen1 = Opiskelija(nimi1, op_nro1)
nimi2 = input("Anna 2. opiskelijan nimi: ")
op_nro2 = input("Anna 2. opiskelijan opiskelijanumero: ")
kurssilainen2 = Opiskelija(nimi2, op_nro2)
print("Anna 1. opiskelijan tenttiarvosana.")
tentti1 = lue_kokonaisluku()
kurssilainen1.muuta_tenttiarvosana(tentti1)
print("Anna 1. opiskelijan harjoitusarvosana.")
harjoitus1 = lue_kokonaisluku()
kurssilainen1.muuta_harjoitusarvosana(harjoitus1)
print("Anna 2. opiskelijan tenttiarvosana.")
tentti2 = lue_kokonaisluku()
kurssilainen2.muuta_tenttiarvosana(tentti2)
print("Anna 2. opiskelijan harjoitusarvosana.")
harjoitus2 = lue_kokonaisluku()
kurssilainen2.muuta_harjoitusarvosana(harjoitus2)
print("1. opiskelijan tiedot:")
print(kurssilainen1.kerro_opiskelijanumero())
print(kurssilainen1.kerro_nimi())
print("Tenttiarvosana:", kurssilainen1.kerro_tenttiarvosana())
print("Harjoitusarvosana:",
kurssilainen1.kerro_harjoitusarvosana())
print("Kurssiarvosana:", kurssilainen1.laske_kokonaisarvosana())
print("2. opiskelijan tiedot:")
print(kurssilainen2.kerro_opiskelijanumero())
print(kurssilainen2.kerro_nimi())
print("Tenttiarvosana:", kurssilainen2.kerro_tenttiarvosana())
print("Harjoitusarvosana:",
kurssilainen2.kerro_harjoitusarvosana())
print("Kurssiarvosana:", kurssilainen2.laske_kokonaisarvosana())
main()
Seuraavaksi esimerkki ohjelman suorituksesta:
Anna 1. opiskelijan nimi: Minni Hiiri
Anna 1. opiskelijan opiskelijanumero: 11223F
Anna 2. opiskelijan nimi: Hessu Hopo
Anna 2. opiskelijan opiskelijanumero: 33441C
Anna 1. opiskelijan tenttiarvosana.
3
Anna 1. opiskelijan harjoitusarvosana.
5
Anna 2. opiskelijan tenttiarvosana.
tekstia
Virheellinen kokonaisluku!
Anna uusi!
1
Anna 2. opiskelijan harjoitusarvosana.
2
1. opiskelijan tiedot:
11223F
Minni Hiiri
Tenttiarvosana: 3
Harjoitusarvosana: 5
Kurssiarvosana: 4
2. opiskelijan tiedot:
33441C
Hessu Hopo
Tenttiarvosana: 1
Harjoitusarvosana: 2
Kurssiarvosana: 2
Usein jokainen luokka kirjoitetaan omaan moduuliinsa (käytännössä omaan
tiedostoonsa) ja myös pääohjelma eri moduuliin kuin luokkien
määrittelyt. Näin tehdään esimerkiksi siksi, että omassa moduulissa
olevaa luokkaa on helppo käyttää hyväksi muissakin ohjelmissa kuin
siinä, jota varten se on alunperin tehty. Jos Opiskelija
-luokka
kirjoitetaan omaan moduuliinsa opiskelija
(tiedoston nimeksi tulee
tällöin opiskelija.py
) ja pääohjelma toiseen moduuliin, on
pääohjelmatiedoston alkuun kirjoitettava rivi
import opiskelija
jotta toisessa moduulissa oleva luokka saataisiin käyttöön. Lisäksi
Opiskelija
-olioita luodessa on annettava luokan sisältävän moduulin
nimi seuraavasti:
kurssilainen1 = opiskelija.Opiskelija(nimi1, op_nro1)
# muita koodiriveja
kurssilainen2 = opiskelija.Opiskelija(nimi2, op_nro2)
Muuten pääohjelma säilyy samanlaisena kuin ennenkin. Esimerkiksi metodien kutsussa ei tarvita moduulin nimeä.
On myös toinen tapa kirjoittaa import-käsky pääohjelmatiedoston alkuun:
from opiskelija import *
Jos käytetään tätä tapaa, ei Opiskelija
-olioita luodessa tarvitse
antaa luokan sisältävän moduulin nimeä, vaan oliot luodaan samalla
tavalla kuin jos luokan koodi olisi samassa moduulissa:
kurssilainen1 = Opiskelija(nimi1, op_nro1)
# muita koodiriveja
kurssilainen2 = Opiskelija(nimi2, op_nro2)
Merkkijonoesitys oliosta
Hyvin monessa ohjelmassa halutaan jossain vaiheessa tulostaa
käsiteltävien olioiden kaikkien kenttien arvot. Tämä voidaan toki tehdä
käyttämällä kenttien arvot palauttavia metodeita ja tulostamalla niiden
palauttamat arvot, kuten edellisessä esimerkissä oli tehty. Se on
kuitenkin aika työläs tapa erityisesti silloin, kun kenttiä on paljon.
Tulostaminen voidaan tehdä helpommin, jos luokkaan kirjoitetaan
erityinen metodi __str__
. Metodi kirjoitetaan siten, että se
palauttaa merkkijonon, joka sisältää käsiteltävän olion kuvauksen,
esimerkiksi kenttien arvot tai muuta haluttua tietoa oliosta. Toisin
sanoen metodi tekee ja palauttaa oliosta merkkijonoesityksen.
Luokkaan Opiskelija
voidaan kirjoittaa (luokan sisään) seuraava
__str__
-metodi:
# Metodi palauttaa merkkijonoesityksen opiskelijan tiedoista.
def __str__(self):
mjono = self.__nimi + ", " + self.__opiskelijanumero + \
", tenttiarvosana: " + str(self.__tenttiarvosana) + \
", harjoitusarvosana: " + str(self.__harjoitusarvosana)
return mjono
Jos metodi on kirjoitettu, luokan olioiden tiedot voidaan tulostaa (yleensä luokan ulkopuolella, esimerkiksi pääohjelmassa) antamalla print-käskyssä vain sen muuttujan nimi, joka viittaa tulostettavaan olioon, esimerkiksi
print(kurssilainen1)
Näin edellisen esimerkkipääohjelman lopussa oleva osa, joka tulostaa
luotujen Opiskelija
-olioiden tiedot voidaan korvata seuraavalla,
lyhyemmällä ja yksinkertaisemmalla ohjelmakoodilla:
print("1. opiskelijan tiedot:")
print(kurssilainen1)
print("Kurssiarvosana:", kurssilainen1.laske_kokonaisarvosana())
print("2. opiskelijan tiedot:")
print(kurssilainen2)
print("Kurssiarvosana:", kurssilainen2.laske_kokonaisarvosana())
Kokonaisarvosanojen tulostaminen on tässäkin koodissa erikseen, koska
__str__
-metodin palauttama merkkijono ei sisällä kokonaisarvosanaa.
Jos pääohjelman loppu muutetaan tällaiseksi, niin edellisen esimerkkiajon suorituksen loppu näyttää seuraavalta:
1. opiskelijan tiedot:
Minni Hiiri, 11223F, tenttiarvosana: 3, harjoitusarvosana: 5
Kurssiarvosana: 4
2. opiskelijan tiedot:
Hessu Hopo, 33441C, tenttiarvosana: 1, harjoitusarvosana: 2
Kurssiarvosana: 2
Tulostus on vähän erilainen kuin edellisessä esimerkkiajossa, koska
__str__
-metodin palauttama merkkijono poikkeaa vähän edellisen
esimerkkiajon pääohjelman tulostuksista.