Tapaaminen 18.02.2010

Aiheina:

  • luokka
  • olio
  • olio-ohjelmointi

Linkkejä:

Luokka

Python on olio-ohjelmointikieli, eli sillä voidaan harjoittaa oliosuuntautunutta ohjelmointia. Perinteisissä proseduraalisissa kielissä ohjelmointi keskittyy funktioiden ja aliohjelmien luomiseen. Olio-ohjemoinnissa taas keskeisessä osassa on itse käsiteltävä data ja siihen liittyvät toiminnot. Eräs tarkoitus tälle lähtökohdalle on halu organisoida ja kapseloida toisiinsa liittyviä asioita yhteen modulaarisiksi kokonaisuuksiksi, jotka sisältävät samalla sekä datan että sen käsittelyyn liittyvät funktiot. Esimerkiksi tason pistettä kuvaavaan pisteolioon liittyy tyypillisesti ainakin sen x- ja y-koordinaatit sekä pisteiden laskeminen yhteen.

Käyttäjän määrittelemä tyyppi

Luokka on pohjimmiltaan käyttäjän itse määrittelemä tietotyyppi. Aivan vastaavasti kuin siis Pythonin sisäänrakennetut lista, monikko, merkkijono ja sanakirja. Määritellään esimerkkinä tason pistettä esittävä luokka Point. (Pythonissa käytäntönä on nimetä luokat isolla alkukirjaimella.) Haluamme luoda pisteitä, jotka sisältävät yhteen “pakettiin” käärittynä kaiken tarvittavan tiedon, siis pisteen x- ja y-koordinaatin. Kahden arvon niputtaminen yhteen voitaisiin tehdä esimerkiksi käyttämällä listaa tai monikkoa. Tämä voi joissain tilanteissa olla käytännöllistä, kun pisteeltä ei tarvitaa monimutkaisempia ominaisuuksia. Määrittelemme nyt kuitenkin oman Point-nimisen tietotyypin, eli luokan.

Määrittely tehdään seuraavasti:

class Point:
    pass

Tämä määrittelee luokan nimeltä Point. Itse luokan määrittely on jätetty tyhjäksi pass-komennolla, joka ei tee mitään vaan merkitsee vain tyhjää paikkaa. Komento pass on tässä tarpeellinen, sillä luokan (samoin kuin funktionkin) määrittelyä ei voi jättää täysin tyhjäksi.

Ohjelmassa käytettävät luokat määritellään yleensä heti alussa import-lauseiden jälkeen.

Luokan määrittely määrittelee tyypin, eli se kuvailee alkioiden ominaisuudet. Itse luokan alkioita sanotaan olioiksi, eli luokan esiintymiksi. Uuden Point-tyyppisen olion voi luoda kutsumalla funktiota nimeltä Point.

>>> type(Point)
<type 'classobj'>
>>> p = Point()
>>> type(p)
<type 'instance'>

Tässä siis Point oli luokka ja muuttujaan p sijoitettiin uusi tämän luokan esiintymä, eli uusi piste.

Attribuutit

Luotuun Point-luokan olioon voidaan nyt lisätä attribuutteja, eli tietoalkioita. Lisätään pisteelle p x- ja y-koordinaatit.

>>> p.x = 3
>>> p.y = 4

Nyt pisteen p x-koordinaattiin voidaan viitata merkinnällä p.x ja y-koordinaattiin merkinnällä p.y.

>>> p.x
3
>>> p.y
4

Oliolle voidaan lisätä attribuutteja tarpeen mukaan, mutta…

Olion alustus

… koska kaikilla luokan Point-olioilla pitää aina olla x- ja y-koordinaatit, ei ole järkevää, että ne jouduttaisiin luomaan kullekin pisteoliolle erikseen. Nythän vasta luodulla pisteoliolla ei ole kumpaakaan näistä attribuuteista. Koska x- ja y-koordinaatit ovat kaksiulotteisten pisteiden olennaiset ominaisuuden, muutetaan luokan määrittelyä niin, että kaikki tämän luokan oliot saavat nämä attribuutit jo luotaessa. Tämä tehdään lisäämällä luokkaan __init___-funktio, joka alustaa uuden olion sitä luotaessa. Todellisuudessa Pythonissa kaikilla luokilla on tällainen funktio, mutta oletuksena alustus ei tee mitään. Tämä funktio suoritetaan aina, kun kutsutaan funtiota Point.

class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

Tässä funktiolle __init__ annetaan argumentteina self, eli uusi Point-olio itse, sekä x- ja y-koordinaatit, joiden oletusarvoina on kummallakin 0.

>>> piste = Point(4,5)
>>> piste.x
4
>>> piste.y
5

Samuus ja samanlaisuus

Olioiden samuus ei ole aivan niin yksinkertaista kuin ensiajattelemalla luulisi.

>>> p1 = Point(2,4)
>>> p1.x, p1.y
(2, 4)
>>> p2.x, p2.y
(2, 4)
>>> p2 = Point(2,4)
>>> p1 == p2
False

Nämä kaksi pistettä olivat siis samanlaisia, eli niillä olivat samat koordinaatit, mutta ne eivät olleet samat.

>>> p3 = p1
>>> p3 == p1
True

Tässä taas muuttujien p1 ja p3 sisältöinä olivat samat pisteoliot, eivätkä vain samanlaiset. Jälkimmäistä tapausta sanotaan pinnalliseksi yhtäsuuruudeksi, koska siinä varrataan vain muuttujien osoittamia muistipaikkoja, ei itse sisältöä. Edellisestä samanlaisuudesta taas käytetään nimitystä syvä yhtäsuuruus, sillä siinä verrataan muuttujissa olevien olioiden sisältöä. Syvän yhtäsuuruuden tarkastamiseksi voidaan kirjoittaa oma funktio:

def same_point(p1, p2):
    return (p1.x == p2.x) and (p1.y == p2.y)

Tämä funktio käy tarkastamassa argumenttina saamiensa pisteiden sisällä, ovatko niiden koordinaatit samat.

>>> same_point(p1,p2)
True

Kopiointi

Sijoituslause muuttuja2 = muuttuja1 ei luo uutta oliota vaan antaa samalle oliolle uuden, vaihtoehtoisen nimen. Tämä voi olla joissain tilanteissa hankalaa, koska yhden muuttujan sisällön muuttaminen saattaa aiheuttaa yllätyksiä, kun ei muisteta, että samaa oliota onkin käytetty jossain toisaalla eri nimellä. Siksi on hyödyllistä pystyä tekemään olioista kopioita. Tähän löytyy funktioita modulista copy.

>>> import copy
>>> p1 = Point()
>>> p1.x = 3
>>> p1.y = 4
>>> p2 = copy.copy(p1)
>>> p1 == p2
False
>>> same_point(p1, p2)
True

Funktio copy.copy()-tutkii argumenttina saamansa olion sisällön ja tekee uuden saman luokan olion, joka saa attribuuteikseen einsimmäisen olion vastaavat attribuutit. Pisteet p1 ja p2 eivät ole samat oliot, mutta niiden koordinaatit ovat samat. Tällaisten yksinkertaisen olion kopiointiin copy-funktio on riittävä, koska olio ei sisällä viittauksia muihin olioihin. Tällainen kopiointi on pinnallista kopiointia.

Pinnallisen kopionnin sijasta tarvitaan syväkopiointia, jos halutaan, että myös uuden olion attribuutit ovat alkuperäisen olion attribuuttien kopioita eivätkä vain uusia nimiä samoille olioille. (Ja niin edelleen aina sisemmille tasoille rekursiivisesti.)