Omat luokat
Luokka määritellään avainsanan class
avulla. Syntaksi on
class LuokanNimi:
# Luokan toteutus
Luokat nimetään yleensä camel case -käytännöllä niin, että sanat kirjoitetaan yhteen ja jokainen sana alkaa isolla alkukirjaimella. Esimerkiksi seuraavat ovat tämän käytännön mukaisia luokkien nimiä:
Pankkitili
OhjelmaApuri
KirjastoTietokanta
PythonKurssinArvosanat
Yhdellä luokalla pyritään mallintamaan jokin sellainen yksittäinen kokonaisuus, jonka sisältämät tiedot liittyvät kiinteästi yhteen. Monimutkaisemmissa ratkaisuissa luokka voi sisältää toisia luokkia (esimerkiksi luokka Kurssi
voisi sisältää luokan Osasuoritus
mukaisia olioita).
Tarkastellaan esimerkkinä yksinkertaista luokkamäärittelyä, josta sisältö vielä puuttuu:
class Pankkitili:
pass
Koodissa määritellään luokka, jonka nimi on Pankkitili
. Luokalle ei ole määritelty varsinaista sisältöä, mutta tästä huolimatta luokasta voidaan muodostaa olio.
Tarkastellaan ohjelmaa, jossa luokasta muodostetun olion sisälle on määritelty kaksi muuttujaa, saldo
ja omistaja
. Olion muuttujia kutsutaan attribuuteiksi. Attribuutista käytetään myös nimitystä oliomuuttuja.
Kun luokasta luodaan olio, voidaan attribuuttien arvoja käsitellä olion kautta:
class Pankkitili:
pass
pekan_tili = Pankkitili()
pekan_tili.omistaja = "Pekka Python"
pekan_tili.saldo = 5.0
print(pekan_tili.omistaja)
print(pekan_tili.saldo)
Pekka Python 5.0
Attribuutit ovat käytettävissä ainoastaan sen olion kautta, jossa ne on määritelty. Pankkitili-luokasta muodostetuilla olioilla on jokaisella omat arvonsa attribuuteille. Attribuuttien arvot haetaan olioiden kautta, esimerkiksi näin:
tili = Pankkitili()
tili.saldo = 155.50
print(tili.saldo) # Viittaa tilin attribuuttiin saldo
print(saldo) # TÄSTÄ TULEE VIRHE, koska oliomuuttuja ei ole mukana!
Konstruktorin lisääminen
Kuten edellisestä esimerkistä huomataan, luokasta voi luoda uuden olion kutsumalla konstruktoria, joka on muotoa LuokanNimi()
. Yleensä olisi kuitenkin kätevä antaa attribuuteille arvot heti kun olio luodaan – nyt esimerkiksi Pankkitilin omistaja ja saldo asetetaan vasta, kun pankkitiliolio on luotu.
Attribuuttien asettamisessa ilman konstruktoria on myös se ongelma, että samasta luokasta luoduilla olioilla voi olla eri attribuutit. Seuraava ohjelmakoodi esimerkiksi antaa virheen, koska oliolle pirjon_tili
ei ole määritelty attribuuttia saldo
:
class Pankkitili:
pass
pekan_tili = Pankkitili()
pekan_tili.omistaja = "Pekka"
pekan_tili.saldo = 1400
pirjon_tili = Pankkitili()
pirjon_tili.omistaja = "Pirjo"
print(pekan_tili.saldo)
print(pirjon_tili.saldo) # TÄSTÄ TULEE VIRHE
Sen sijaan että attribuuttien arvot alustettaisiin luokan luomisen jälkeen, on huomattavasti parempi ajatus alustaa arvot samalla, kun luokasta luodaan olio.
Konstruktori kirjoitetaan luokan sisään metodina __init__
yleensä heti luokan alkuun.
Tarkastellaan Pankkitili
-luokkaa, johon on lisätty konstruktori:
class Pankkitili:
# Konstruktori
def __init__(self, saldo: float, omistaja: str):
self.saldo = saldo
self.omistaja = omistaja
Konstruktorin nimi on aina __init__
. Huomaa, että nimessä sanan init
molemmilla puolilla on kaksi alaviivaa.
Konstruktorin ensimmäinen parametri on nimeltään self
. Tämä viittaa olioon, jota käsitellään. Asetuslause
self.saldo = saldo
asettaa parametrina annetun saldon luotavan olion saldoksi. On tärkeä huomata, että tässä yhteydessä muuttuja self.saldo
on eri muuttuja kuin muuttuja saldo
:
-
Muuttuja
self.saldo
viittaa olion attribuuttiin. Jokaisella Pankkitili-luokan oliolla on oma saldonsa. -
Muuttuja
saldo
on konstruktorimetodin__init__
parametri, jolle annetaan arvo, kun metodia kutsutaan (eli kun halutaan luoda uusi olio luokasta).
Nyt kun konstruktorille on määritelty parametrit, voidaan attribuuttien arvot antaa oliota luotaessa:
class Pankkitili:
# Konstruktori
def __init__(self, saldo: float, omistaja: str):
self.saldo = saldo
self.omistaja = omistaja
# Parametrille self ei anneta arvoa, vaan Python antaa sen
# automaattisesti
pekan_tili = Pankkitili(100, "Pekka Python")
pirjon_tili = Pankkitili(20000, "Pirjo Pythonen")
print(pekan_tili.saldo)
print(pirjon_tili.saldo)
100 20000
Esimerkistä huomataan, että olioiden luominen helpottuu, kun arvot voidaan antaa heti oliota muodostaessa. Samalla tämä varmistaa, että arvojen antaminen ei unohdu, ja ohjaa käyttäjää antamaan arvot attribuuteille.
Attribuuttien arvoja voi edelleen muuttaa myöhemmin ohjelmassa, vaikka alkuarvo olisikin annettu konstruktorissa:
class Pankkitili:
# Konstruktori
def __init__(self, saldo: float, omistaja: str):
self.saldo = saldo
self.omistaja = omistaja
pekan_tili = Pankkitili(100, "Pekka Python")
print(pekan_tili.saldo)
# Saldoksi 1500
pekan_tili.saldo = 1500
print(pekan_tili.saldo)
# Lisätään saldoon 2000
pekan_tili.saldo += 2000
print(pekan_tili.saldo)
100 1500 3500
Tarkastellaan vielä toista esimerkkiä luokasta ja olioista. Kirjoitetaan luokka, joka mallintaa yhtä lottokierrosta:
from datetime import date
class LottoKierros:
def __init__(self, viikko: int, pvm: date, numerot: list):
self.viikko = viikko
self.pvm = pvm
self.numerot = numerot
# Luodaan uusi lottokierros
kierros1 = LottoKierros(1, date(2021, 1, 2), [1,4,8,12,13,14,33])
# Tulostetaan tiedot
print(kierros1.viikko)
print(kierros1.pvm)
for numero in kierros1.numerot:
print(numero)
1 2021-01-02 1 4 8 12 13 14 33
Attribuutit voivat olla siis minkä tahansa tyyppisiä – esimerkiksi edellisessä esimerkissä jokaiseen olioon tallennetaan lista ja päivämäärä.
Omien luokkien olioiden käyttö
Omasta luokasta muodostetut oliot käyttäytyvät esimerkiksi funktioiden parametrina ja paluuarvona samalla tavalla kuin muutkin oliot. Voisimme esimerkiksi tehdä pari apufunktiota tilien käsittelyyn:
# funktio luo uuden tiliolion ja palauttaa sen
def avaa_tili(nimi: str):
uusi_tili = Pankkitili(0, nimi)
return uusi_tili
# funktio asettaa parametrina saamansa rahasumman parametrina olevalle tilille
def laita_rahaa_tilille(tili: Pankkitili, summa: int):
tili.saldo += summa
pekan_tili = avaa_tili("Pekka Python")
print(pekan_tili.saldo)
laita_rahaa_tilille(pekan_tili, 500)
print(pekan_tili.saldo)
0 500