Luento 17: Perintä class Staff(object): def init (self, name, salary): self.salary = salary self.status = 'Staff' def set_name(self, new_name): self.name = new_name def get_status(self): return self.status def get_salary(self): return self.salary def raise_salary(self, new_salary): if self.salary < new_salary: self.salary = new_salary + ' ' + self.status + ' ' + str(self.salary) class Student(object): def init (self, name): self.points = 0 self.status = 'Student' def set_name(self, new_name): self.name = new_name def get_status(self): return self.status def add_points(self, new_points): self.points += new_points + ' ' + self.status + ' ' + str(self.points)
Olemme jo tottuneet siihen, että jokaiseen luokkaan pitää kirjoittaa kaikki ne metodit, joiden mukaisia toimintoja siitä luodun olion halutaan suorittavan. Toisaalta taas, on sanottu että toistuvat osuudet pitää laittaa funktioihin/metodeihin. Mitä siis tehdä, kun kahdessa luokassa on esitetty täsmälleen samat asiat, eli niissä on toistoa. Niin, ja jos noita kohtia joutuisi muuttamaan, niin samat muutokset pitäisi tehdä kumpaankin luokkaan. Ei siis mitenkään helposti ylläpidettävää ohjelmakoodia. Olio- ohjelmoinnissa on käsite perintä, jota voisi käyttää hyväksi tässä tapauksessa. Perintä Perintä eli periytyminen (engl. inheritance) on tekniikka, jonka avulla voi määritellä ylä- ja alakäsitteitä. Perinnässä ilmoitetaan koodissa, että tietty luokka ("aliluokka") sisältää kaikki tietyn toisen luokan ("yliluokan") piirteet, mukaan lukien metodien toteutuksen ja ilmentymämuuttujat. Lisäksi jokaisella aliluokalla voi olla omiakin piirteitä. Perintärelaatiolla voi muodostaa hierarkioita ihan niin kuin tuossa eläinten luokituksessakin. Edellisen tai seuraavan "sukupolven" luokkia samassa "sukupuun haarassa" kutsutaan välittömiksi yli- ja aliluokiksi (engl. direct/immediate super- /subclass). Koska kaikki pythonissa on olioita, nekin kuuluvat erilaisiin aliluokkiin. Jokaisella aliluokalla on yliluokkiensa ominaisuudet ja lisäksi vielä omia ominaisuuksia.
Muutetaan alun esimerkin henkilökunta ja opiskelija käyttämään perintää. Ensin tarvitaan yhteinen nimi noille luokille: mitä ne molemmat kuvaavat. Tässä noita yhdessä voisi kutsua vaikka henkilöiksi (person). Seuraavaksi kerätään kummastakin luokasta kaikki metodit ja muuttujat, jotka ovat aivan samoja kummassakin. Metodit def set_name(self, new_name): self.name = new_name def get_status(self): return self.status ovat täsmälleen toistensa kopioita. Loput asiat luokissa ovatkin sitten erilaisia. Tehdään noista valituista yliluokka Person: class Person(object): Perii kaiken luokasta object def set_name(self, new_name): self.name = new_name def get_status(self): return self.status Tämä näyttää aivan tavalliselta luokalta. Ainoa mikä puuttuu, on alustusmetodi mutta se ei haittaa, jos käytämme aliluokissa alkuperäisiä alustusmetodeja. Sitten laitetaan vain sekä opiskelija että henkilökunta perimään nämä ominaisuudet: class Staff(Person): def init (self, name, salary): self.salary = salary self.status = 'Staff' Perii kaiken luokasta Person class Student(Person): def init (self, name): self.points = 0 self.status = 'Student' Perii kaiken luokasta Person Nyt sekä Staff, että Student- luokilla on molemmilla käytettävissään metodit set_name(), get_name() ja get_status() ja luokkia voi jo testata näiden osalta:
from register import Staff from register import Student def main(): proffa = Staff('H. Ajamieli', 4000) siivooja = Staff('S. Iivooja', 2000) olli = Student('O. Opiskelija') teemu = Student('Teemu Teekkari') print proffa.get_name() print teemu.get_status() H. Ajamieli Student if name == ' main ': main() Nyt on vuorossa loppujen metodien lisääminen luokkiin. class Staff(Person): def init (self, name, salary): self.salary = salary self.status = 'Staff' def get_salary(self): return self.salary def raise_salary(self, new_salary): if self.salary < new_salary: self.salary = new_salary + ' ' + self.status + ' ' + str(self.salary) class Student(Person): def init (self, name): self.points = 0 self.status = 'Student' def add_points(self, new_points): self.points += new_points + ' ' + self.status + ' ' + str(self.points)
Yliluokka Person sisältää kaikki metodit, jotka ovat yhteisiä kaikille henkilöille. Siitä tehdyt aliluokat voivat käyttää kaikkia yliluokan metodeja ja lisäksi omia metodeja. Perintää kuvataan nuolikuviolla. Student Person Staff Kerätään tulos yhteen: class Person(object): def set_name(self, new_name): self.name = new_name def get_status(self): return self.status class Staff(Person): def init (self, name, salary): self.salary = salary self.status = 'Staff' def get_salary(self): return self.salary def raise_salary(self, new_salary): if self.salary < new_salary: self.salary = new_salary + ' ' + self.status + ' ' + str(self.salary) class Student(Person): def init (self, name): self.points = 0 self.status = 'Student' def add_points(self, new_points): self.points += new_points + ' ' + self.status + ' ' + str(self.points)
Ohjelma lyheni jonkun verran. Nyt jos yhteisiä metodeja tarvitsee muuttaa, muutokset tehdään vain yhteen kohtaan ohjelmakoodia. Lisäksi testaus pitäisi onnistua samalla testimoduulilla kuin aikaisemminkin. Perinnän lisäksi tämä toimii siis esimerkkinä ohjelman uudelleenrakentamisesta (refactoring). Siinä ohjelman toteutustavan muuttaminen ei saa vaikuttaa ohjelman toimintaan. Samojen testien pitäisi antaa samat tulokset kummassakin tapauksessa. Tehdään siis testi aiemmalla testiohjelmalla (regressiotestaus) ja todetaan, että toimii samoin edelleen. Katsotaanpa sitten toista esimerkkiä. Mikä voisi olla lemmikkien yhdistävä tekijä? Tehdään siis luokat lemmikeille ja otetaan yhdeksi jo aiemminkin esillä ollut papukaija. Toinen olkoon vaikkapa kissa. Molempia kutsutaan joksikin, niillä on siis nimet ja ne kuuluvat jompaankumpaan lajiin. Lisäksi ne ääntelevät jollain tavalla. Kissoilla on oma äänivalikoimansa ja papukaija voi osata puhua. Tehdään näillä tiedoilla yliluokka Pet: class Pet(object): def init (self, name, species): self.species = species def get_species(self): return self.species def speak(self): index = random.randint(0, len(self.repertoire)-1) return self.repertoire[index] return '{:s} is a {:s}'.format(self.name, self.species) Lemmikin luonnin yhteydessä kerrotaan lemmikin nimi ja laji. Lemmikkiä voi käskeä kertomaan nimensä ja lajinsa sekä päästämään jonkin äänen. Loppuun on lisätty olion tulostusta varten str - metodi. Metodi palauttaa merkkijonona lemmikin nimen ja sen lajin. Mitä ominaisuuksia kissalla voisi olla näiden lisäksi. Ne karvapallot tuntien vaikka mitä, mutta otetaan nyt vain kunkin kissan herkkuruoka. Tehdään kissalle oma alustusmetodi, joka täydentää lemmikkiluokan alustusmetodia. class Cat(Pet): def init (self, name, goodies): Pet. init (self, name, "Cat") self.goodies = goodies self.repertoire = ['Meow!', 'Hrrrr', 'Hiss!'] def get_goodies(self): return self.goodies
Kun luodaan uusi kissaolio, kerrotaan kissan nimi ja sen herkkuruoka. Koska kissalla on oma alustusmetodi, se syrjäyttää lemmikin alustusmetodin. Lemmikin alustusmetodissa on kuitenkin nimen ja lajin talteenotto, joten sitä ei kannata hylätä kokonaan. Otetaan ensin kissan nimi ja lajitieto talteen kutsumalla lemmikin alustusmetodia. class Pet(object): class Cat(Pet): def init (self, name, goodies): Pet. init (self, name, "Cat") def init (self, name, species): self.species = species Yliluokan nimi Yliluokan metodi Kutsuva olio Yliluokan metodin vaatimat parametrit Herkkuruoka ja äänivalikoima alustetaan metodin lopuilla riveillä. Kissoilla ei ole mitään suurempaa tarvetta mielistellä omistajaansa, joten sen äänivalikoima on suunnilleen vakio. Naukaisut, kehräykset ja sähinät voi alustaa kaikille samoiksi. Kissojen oma metodi on herkkuruoan haku. Mitä omaa on papukaijalla? Tässä tapauksessa väri ja lauseet, jotka se on mahdollisesti oppinut. Jokainen kaija osaa kuitenkin perus kraah- äänen. class Parrot(Pet): def init (self, name, color, repertoire=[]): Pet. init (self, name, "Parrot") self.color = color self.repertoire = ['Kraah'] self.repertoire.extend(repertoire) return '{:s} is a {:s} {:s}.'.format(self.get_name(), self.color, self.get_species()) Jos papukaijaa luotaessa lauselista jätetään antamatta, sen tilalle tulee tyhjä lista (repertoire=[]). Samoin kuin kissan tapauksessa, nimi ja laji otetaan talteen yliluokan alustusmetodin kautta ja papukaijan omat tiedot, kuten väri ja mahdolliset opitut lauseet, otetaan ilmentymämuuttujiin alustusmetodin loppuosassa. Luokalle on tehty myös oma str - metodi korvaamaan yliluokan vastaavan metodin. Papukaijan kohdalla halutaan näkyviin myös linnun väri.
Tuo korvaaminen (overriding) toimii siten, että kutsuttaessa olion metodia tulkki selaa luokkia perimishierarkiassa alhaalta ylöspäin, eli aliluokista yliluokkiin ja käyttää ensimmäistä vastaantulevaa oikeannimistä metodia. Kissan str löytyy siis vasta yliluokasta mutta papukaijan jo omasta luokasta. Papukaijan str - metodi siis korvaa yliluokan vastaavan ja tulkki käyttää aina sitä. Moniperintä Pythonissa luokka voi periä ominaisuuksia useammalta luokalta samaan aikaan. Olkoon yksinkertaisena esimerkkinä seuraava: class Horse(object): def init (self, age): self.age = age def trot(self): print 'klop klop' def whinny(self): print 'Ihahaa' class Bird(object): def init (self, wingspan): self.weight = wingspan def chirp(self): print 'chirp chirp' def fly(self): print 'flap flap' class Pegasus(Horse, Bird): def init (self, age, wingspan): Horse. init (self, age) Bird. init (self, wingspan) Hevonen ja lintu ovat normaaleja luokkia. Kummastakin voi luoda olioita tavalliseen tapaan. Hevonen hirnuu ja ravaa, lintu lentää ja sirkuttaa. Entäpä sitten Pegasus? Sehän on se siivekäs hevonen kreikan mytologiasta. Ravaamisen lisäksi se osaa siis lentää. Pegasus on tässä tehty periyttämällä sille sekä hevosen että linnun ominaisuudet. Periyttämällä luokka useammasta yliluokasta siihen saadaan kaikkien näiden yliluokkien ominaisuudet. Siivekkäät hevoset osaavat siis hirnua ravata, lentää ja sirkuttaa. Tuolle sirkuttamiselle tosin täytyy tehdä jotain: sen voisi vaikka syrjäyttää Pegasus- luokassa ja laittaa pegasuksen chirp- metodin kutsumaan hevosen whinny- metodia. Näin pegasus- olion chirp- metodia kutsuttaessa kuuluisikin hirnuntaa.
class Pegasus(Horse, Bird): def init (self, age, wingspan): Horse. init (self, age) Bird. init (self, wingspan) def chirp(self): Horse.whinny(self) Jos useammasta luokasta periytetylle luokalle ei tee omaa alustusmetodia, tulkki hakee sitä yliluokista. Yliluokat kokeillaan siinä järjestyksessä kuin ne luokan otsikossa ovat. Tässä tapauksessa siis käytettäisiin Horse- luokan init - metodia, jos sellainen on ja jos ei ole, kokeillaan Bird luokkaa. Molempia ei siis käytetä automaattisesti, vaan ensimmäistä löytyvää. Yleensä kannattaakin tehdä moniperintää käyttävälle aliluokalle oma alustusmetodi, joka kutsuu yliluokkiensa alustusmetodeja. Testiohjelma: from horses import * def main(): print '----Usual horse-----' horse1 = Horse(5) horse1.whinny() horse1.trot() print '----Flying horse-----' horse2 = Pegasus(3, 6.5) horse2.whinny() horse2.trot() horse2.fly() horse2.chirp() if name == ' main ': main() Ja sen tulostus: ----Usual horse----- Ihahaa klop klop ----Flying horse----- Ihahaa klop klop flap flap chirp chirp
Super Toinen tapa kutsua yliluokan metodia syrjäyttävästä luokasta on käyttää super- funktiota. Sitä käytettäessä yliluokkaa ei tarvitse nimetä, vaan Python hoitaa vastaavan metodin etsimisen yliluokista. Pegasuksen chirp- metodin tapauksessa sen käyttö olisi seuraava: class Pegasus(Horse, Bird): def init (self, age, wingspan): Horse. init (self, age) Bird. init (self, wingspan) def chirp(self): super(pegasus, self).whinny() Funktiolle annetaan parametreina luokka, jossa kutsu sijaitsee sekä self. Funktiolle annetaan metodina sen funktion nimi, jota halutaan kutsua. Huomaa, että kutsusta puuttuu self mutta muut mahdolliset parametrit sille pitää antaa. super- fuktio toimii tässä tapauksessa ihan oikein mutta monimutkaisemmissa luokkarakenteissa sen oikeaoppinen käyttö vaatii lisätyötä, joka ei ole enää tämän kurssin asioita. Lisäksi sen käyttö vaatii uuden luokkamäärittelyn mukaisia luokkia, joissa luokan nimen perässä on aina se, mistä se periytyy. class Horse(object): Vanhan tyylin mukaisilla luokkamäärittelyillä, joissa yliluokka piti kertoa vain silloin, jos se oli jotakin itse tehtyä (ei Pythonin sisäänrakennettu tyyppi) class Horse: super ei toimi.