Osa 8

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)
Esimerkkitulostus

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)
Esimerkkitulostus

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)
Esimerkkitulostus

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)
Esimerkkitulostus

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ä.

Loading
Loading

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)
Esimerkkitulostus

0 500

Loading
Loading
Loading