Osa 9

Lisää esimerkkejä

Tarkastellaan seuraavaksi esimerkkiä, joka muodostuu kahdesta luokasta. Luokka Piste mallintaa yhtä pistettä kaksiulotteisessa koordinaatistossa ja luokka Jana kahden pisteen välistä janaa. Luokkien toiminta on kommentoitu koodiin.

import math

class Piste:
    """ Luokka mallintaa pistettä kaksiulotteisessa koordinaatistossa """

    def __init__(self, x: float, y: float):
        # Attribuutit ovat julkisia, koska mitkä tahansa arvot käyvät x:n ja y:n arvoiksi
        self.x = x
        self.y = y

    # Luokkametodi palauttaa uuden pisteen paikassa (0, 0)
    # Huomaa, että luokan sisältä voi palauttaa olion luokasta
    @classmethod
    def origo(cls):
        return Piste(0, 0)

    # Luokkametodi muodostaa uuden pisteen annetun pisteen perusteella
    # Uusi piste on peilikuva annetusta pisteestä jommankumman tai molempien akselien suhteen
    # Esimerkiksi pisteen (1, 3) peilikuva x-akselin suhteen on (-1, 3)
    @classmethod
    def peilikuva(cls, piste, peilaa_x: bool, peilaa_y: bool):
        x = piste.x
        y = piste.y
        if peilaa_x:
            x = -x
        if peilaa_y:
            y = -y

        return Piste(x, y)

    def __str__(self):
        return f"({self.x}, {self.y})"

class Jana:
    """ Luokka mallintaa janaa kaksiulotteisessa koordinaatistossa """

    def __init__(self, alku: Piste, loppu: Piste):
        # Attribuutit ovat julkisia, koska mitkä tahansa pisteet kelpaavat
        self.alku = alku
        self.loppu = loppu

    # Metodi laskee janan pituuden Pythagoraan lauseella
    def pituus(self):
        summa = (self.loppu.x - self.alku.x) ** 2 + (self.loppu.y - self.alku.y) ** 2
        return math.sqrt(summa)

    # Metodi palauttaa janan keskipisteen
    def keskipiste(self):
        keskix = (self.alku.x + self.loppu.x) / 2
        keskiy = (self.alku.y + self.loppu.y) / 2
        return Piste(keskix, keskiy)

    def __str__(self):
        return f"{self.alku} ... {self.loppu}"
piste = Piste(1,3)
print(piste)

origo = Piste.origo()
print(origo)

piste2 = Piste.peilikuva(piste, True, True)
print(piste2)

jana = Jana(piste, piste2)
print(jana.pituus())
print(jana.keskipiste())
print(jana)
Esimerkkitulostus

(1, 3) (0, 0) (-1, -3) 6.324555320336759 (0.0, 0.0) (1, 3) ... (-1, -3)

Parametrien oletusarvot

Pythonissa mille tahansa parametrille voidaan asettaa oletusarvo. Oletusarvoja voidaan käyttää sekä funktioiden että metodien parametreissa.

Jos parametrille on annettu oletusarvo, sille ei ole pakko antaa arvoa kutsuttaessa. Jos arvo annetaan, se syrjäyttää oletusarvon, ja jos arvoa ei anneta, käytetään oletusarvoa.

Oletusarvot ovat usein hyödyllisiä konstruktoreissa: jos on oletettavaa, ettei tiettyä tietoa ole aina olemassa oliota luodessa, on parempi antaa sille vakioarvo konstruktorissa kuin antaa tämä asiakkaan huoleksi. Tämä on asiakkaalle helpompaa ja myös ylläpitää olion sisäistä eheyttä, kun voidaan esimerkiksi olla varmoja, että "tyhjä" arvo on aina samanlainen (muuten se voisi olla esimerkiksi merkkijono "", arvo None tai merkkijono "ei asetettu").

Tarkastellaan esimerkkinä luokkaa, joka mallintaa opiskelijaa. Pakollisia kenttiä luodessa ovat opiskelijanumero ja nimi ja näistä opiskelijanumeroa ei pysty myöhemmin muuttamaan. Opintopisteet ja muistiinpanot voi halutessaan antaa oliota luodessa, mutta niille on myös asetettu oletusarvot. Luokan toiminta on kommentoitu suoraan ohjelmakoodin yhteyteen.

class Opiskelija:
    """ Mallintaa yhtä opiskelijaa """

    def __init__(self, nimi: str, opiskelijanumero: str, opintopisteet:int = 0, muistiinpanot:str = ""):
        # kutsuu asetusmetodia
        self.nimi = nimi

        if len(opiskelijanumero) < 5:
            raise ValueError("Opiskelijanumerossa tulee olla vähintään 5 merkkiä")

        self.__opiskelijanumero = opiskelijanumero

        # Kutsuu asetusmetodia
        self.opintopisteet = opintopisteet

        self.__muistiinpanot = muistiinpanot

    @property
    def nimi(self):
        return self.__nimi

    @nimi.setter
    def nimi(self, nimi):
        if nimi != "":
            self.__nimi = nimi
        else:
            raise ValueError("Nimi ei voi olla tyhjä")

    @property
    def opiskelijanumero(self):
        return self.__opiskelijanumero

    @property
    def opintopisteet(self):
        return self.__opintopisteet

    @opintopisteet.setter
    def opintopisteet(self, op):
        if op >= 0:
            self.__opintopisteet = op
        else:
            raise ValueError("Opintopisteet ei voi olla negatiivinen luku")

    @property
    def muistiinpanot(self):
        return self.__muistiinpanot

    @muistiinpanot.setter
    def muistiinpanot(self, muistiinpanot):
        self.muistiinpanot = muistiinpanot

    def yhteenveto(self):
        print(f"Opiskelija {self.__nimi} ({self.opiskelijanumero}):")
        print(f"- opintopisteitä {self.__opintopisteet}")
        print(f"- muistiinpanot: {self.muistiinpanot}")
# Annetaan pelkkä nimi ja op.nro
opiskelija1 = Opiskelija("Olli Opiskelija", "12345")
opiskelija1.yhteenveto()

# Annetaan nimi, op.nro ja opintopisteet
opiskelija2 = Opiskelija("Outi Opiskelija", "54321", 25)
opiskelija2.yhteenveto()

# Annetaan kaikki tiedot
opiskelija3 = Opiskelija("Olavi Opiskelija", "99999", 140, "lisäaika tentissä")
opiskelija3.yhteenveto()

# Ei anneta opintopisteitä, mutta annetaan muistiinpanot
# Huomaa, että parametri pitää nyt nimetä, kun järjestys eroaa tavallisesta
opiskelija4 = Opiskelija("Onerva Opiskelija", "98765", muistiinpanot="poissaoleva lukuvuonna 20-21")
opiskelija4.yhteenveto()
Esimerkkitulostus

Opiskelija Olli Opiskelija (12345):

  • opintopisteitä 0
  • muistiinpanot: Opiskelija Outi Opiskelija (54321):
  • opintopisteitä 25
  • muistiinpanot: Opiskelija Olavi Opiskelija (99999):
  • opintopisteitä 140
  • muistiinpanot: lisäaika tentissä Opiskelija Onerva Opiskelija (98765):
  • opintopisteitä 0
  • muistiinpanot: poissaoleva lukuvuonna 20-21

Huomaa, että attribuutille opiskelijanumero ei ole määritelty asetusmetodia, koska ideana on, että opiskelijanumero ei voi muuttua.

Parametrien oletusarvojen käyttöön liittyy kuitenkin eräs huomattavan iso "mutta" joka ilmenee seuraavasti esimerkistä:

class Opiskelija:
    def __init__(self, nimi, tehdyt_kurssit=[]):
        self.nimi = nimi
        self.tehdyt_kurssit = tehdyt_kurssit

    def lisaa_suoritus(self, kurssi):
        self.tehdyt_kurssit.append(kurssi)
opiskelija1 = Opiskelija("Olli Opiskelija")
opiskelija2 = Opiskelija("Outi Opiskelija")

opiskelija1.lisaa_suoritus("Ohpe")
opiskelija1.lisaa_suoritus("Tira")

print(opiskelija1.tehdyt_kurssit)
print(opiskelija2.tehdyt_kurssit)
Esimerkkitulostus

['Ohpe', 'Tira'] ['Ohpe', 'Tira']

Huomataan siis, että kurssisuorituksen lisääminen Ollille muuttaa myös Outin kurssisuorituksia. Ilmiö johtuu siitä, että Python uudelleenkäyttää oletusarvoa. Yllä oleva tapa luoda opiskelijat vastaa siis seuraavaa koodia:

kurssit = []
opiskelija1 = Opiskelija("Olli Opiskelija", kurssit)
opiskelija2 = Opiskelija("Outi Opiskelija", kurssit)

Tästä johtuen parametrin oletusarvona ei koskaan tulisi käyttää monimutkaisempia tietorakenteita kuten listoja. Korjattu versio luokan Opiskelija konstruktorista on seuraava:

class Opiskelija:
    def __init__(self, nimi, tehdyt_kurssit=None):
        self.nimi = nimi
        if tehdyt_kurssit is None:
            self.tehdyt_kurssit = []
        else:
            self.tehdyt_kurssit = tehdyt_kurssit

    def lisaa_suoritus(self, kurssi):
        self.tehdyt_kurssit.append(kurssi)
opiskelija1 = Opiskelija("Olli Opiskelija")
opiskelija2 = Opiskelija("Outi Opiskelija")

opiskelija1.lisaa_suoritus("Ohpe")
opiskelija1.lisaa_suoritus("Tira")

print(opiskelija1.tehdyt_kurssit)
print(opiskelija2.tehdyt_kurssit)
Esimerkkitulostus

['Ohpe', 'Tira'] []

Loppuhuipennus

Vaikka seuraava tehtävä on tässä luvussa, et tarvitse tehtävän ratkaisemiseen mitään muuta kun luvussa Oliot attribuuttina esiteltyjä tekniikoita. Tehtävä on käytännössä hyvin samanlainen kuin tuon luvun tehtävät lahjapakkaus ja huoneen lyhin.

Loading

Vastaa lopuksi osion loppukyselyyn:

:
Loading...
:
Loading...

Log in to view the quiz