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:
y = -y
if peilaa_y:
x = -x
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)
(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()
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)
['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)
['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.
Vastaa lopuksi osion loppukyselyyn:
Log in to view the quiz