13. OBJEKT-ORIENTEERITUD PROGRAMMEERIMINE
Klassid kui tabelid
Varem oleme vaadanud, kuidas andmeid? hoitakse andmebaasides ning kuidas neid andmeid andmebaasist kätte saada. Sellel nädalal vaatame, kuidas andmeid saab hoida objektides.
Objektis informatsiooni hoidmiseks peame läbi mõtlema, kuidas soovime objektis andmeid kirjeldada. Sarnaste objektide jaoks ühiste reeglite sätestamiseks kasutame klasse. Klassid on nagu andmebaaside tabelid, mis ütlevad, millist infot me iga tabelis oleva isendi kohta teame. Klassi defineerimiseks kasutatakse märksõna class
. Sellele märksõnale järgneb klassi nimi ja koolon. Klasside nimed kirjutatakse suure algustähega ja peavad olema unikaalsed. Klassi sisu kirjutatakse taandega.
class Õppija: # klassi sisu
Meenutame Õppija
tabelit: igal Õppijal
on enda matriklinumber
, eesnimi
, perekonnanimi
ja isikukood
. Need neli tunnust võime lugeda muutujateks, mida väärtustame iga Õppija
jaoks eraldi.
Muutujaid, mis kehtivad iga isendi enda kohta, nimetatakse isendimuutujateks ehk isendiväljadeks.
Pythonis kasutatakse nende klassisiseseks eristamiseks märksõna self
. Isendivälju saame kasutada selle märksõna kaudu.
Nõnda saame klassis Õppija
defineerida muutujad self.matrikli_nr
, self.eesnimi
, self.perenimi
ja self.isikukood
. Klassist väljaspool nende muutujate kasutamisel kasutatakse self
märksõna asemel vastavat isendit hoidva muutuja nime. self
märksõna kasutatakse ka isendi informatsiooni kasutavate klassis defineeritud funktsioonide (meetodite) esimese argumendina. Ühte levinud isendispetsiifilist funktsiooni vaatleme järgmisena.
Konstruktor
Konstruktor on klassile iseloomulik funktsioon, kus defineeritakse isendimuutujad. Konstruktorit kasutatakse iga isendi loomisel isendimuutujate väärtustamiseks. Funktsiooni nimeks on __init__
, mis tuleneb uue isendi initsialiseerimisest ehk lähtestamisest. Funktsiooni parameetriteks on esmalt märksõna self
ning seejärel kõik teised muutujad, mida soovime isendisse salvestada. Konstruktori sees omistatakse need väärtused vastavatele isendimuutujatele ehk isendiväljadele. Argumendi ja isendimuutuja nimi ei pea olema sama, aga võib olla.
class Õppija: def __init__(self, matrikli_nr, eesnimi, perenimi, isikukood): self.matrikli_nr = matrikli_nr self.eesnimi = eesnimi self.perenimi = perenimi self.isikukood = isikukood
Uue isendi loomine
Uue isendi loomisel soovime isendimuutujad väärtustada ehk omistada neile mingid väärtused. Loodud konstruktorit saame kasutada uue isendi loomisel. Kui konstruktori __init__
defineerimine oli nagu andmebaaside CREATE TABLE
käsk, siis konstruktori kasutamine on analoogne INSERT INTO
käsuga:
õppur1 = Õppija("A034", "Albert", "Paas", 34105212737) õppur2 = Õppija("A037", "Pearu", "Murakas", 34206122154) print(õppur1.eesnimi)
Albert
print(õppur2.eesnimi)
Pearu
Sarnaselt üksikute muutujate lugemisele võime isendimuutujaid ka ümber väärtustada või lisada.
õppur1.lemmikloom = "koer" print(õppur1.lemmikloom)
Väljastatakse "koer"
.
Meetodid
Klassis saame luua meetodeid ehk isendispetsiifilisi funktsioone, mis määravad objekti käitumise. Nii võime sama meetodi välja kutsuda mistahes Õppija
isendi peal, saades isendi andmetest sõltuva tulemuse.
def sugu(self): if self.isikukood//10000000000%2 == 1: # Vaatleme, kas isikukoodi esimene number on paaris- või paaritu arv. return "M" else: return "N"
Meetodite väljakutsumine on sarnane sõnede peal split
ja strip
meetodite kasutusega:
print(õppur1.sugu())
M
Kui soovime välja printida informatsiooni isendi kohta, siis üheks variandiks on üksikute isendiväljade kasutamine. print(õppur1)
annab aga hetkel eriskummalise tulemuse
<__main__.Õppija object at 0x02BFBE90>
, mis ütleb, et tegu on isendiga klassist Õppija
ning see asub mäluväljal 0x02BFBE90
. Selleks, et print
käsk annaks meile informatiivsema kirjelduse, tuleb klassis defineerida meetod __str__
:
def __str__(self): return "Õppija nimi: " + self.eesnimi + " " + self.perenimi + "."
Staatilised muutujad ja funktsioonid
Lisaks isendispetsiifilistele muutujatele ja funktsioonidele võib klassis defineerida ka kõikidele sama klassi isenditele ühiseid muutujaid ja funktsioone. Neid nimetatakse staatilisteks, kuna nad ei muutu sõltuvalt isendist. Staatilisi muutujaid võib võrrelda globaalsete muutujatega, mispuhul isendispetsiifilised muutujad on sarnased lokaalsete muutujatega - kasutatakse funktsioonides.
Sarnaselt globaalsete muutujatega deklareeritakse staatilised muutujad klassi sees funktsiooniväliselt. Erinevalt isendispetsiifiliste muutujatest tuleb staatiliste muutujate lugemiseks või muutmiseks pöörduda isendi asemel terve klassi poole. Kui meie näites oleks eesnimi staatiline muutuja, tuleks õppur1.eesnimi
asemel kasutada Õppija.eesnimi
. Seejuures saab, kuid ei ole soovitatav, kasutada sama nimega staatilisi ja isendispetsiifilisi muutujaid.
Staatilised funktsioonid erinevad isendispetsiifilistest vaid paari omaduse poolest: parameetrites ei kasutata märksõna self
, funktsioonis sees ei saa mittestaatilisi isendimuutujaid kasutada ning funktsiooni väljakutsumiseks kasutatakse isendi muutujanime asemel klassi üldnimetust.
Võime lisada klassi algusesse ühe muutuja kool
, ühe staatilise funktsiooni ning muuta väljaprinditavat infot:
class Õppija: kool = "Tartu Ülikool" def kusÕpib(): return "Õpib koolis " + Õppija.kool + "." def __str__(self): return "Õppija nimi: " + self.eesnimi + " " + self.perenimi + ".\n" + Õppija.kusÕpib()
Nüüd on huvitav uurida print(õppur1)
ja print(õppur2)
väljundit.
# Väljastamine, kasutades isendispetsiifilist funktsiooni koos staatilise muutuja ja funktsiooniga print(õppur1)
Õppija nimi: Albert Paas. Õpib koolis Tartu Ülikool.
print(õppur2)
Õppija nimi: Pearu Murakas. Õpib koolis Tartu Ülikool.
Isendid andmestruktuurides
Tähele võib panna ka võimalust panna isendeid teistesse andmestruktuuridesse nagu järjend.
õppurid = [õppur1, õppur2] for el in õppurid: print(el.eesnimi, el.perenimi + ", isikukood:", el.isikukood)
Albert Paas, isikukood: 34105212737 Pearu Murakas, isikukood: 34206122154
Lõpetuseks
Oleme vaadelnud, kuidas ehitada objekt erinevate omadustega. Objekte on hea kasutada olukordades, kus ühe ja sama väärtusega on seotud hästi palju teisi. Õppija
klassi asemel oleksime saanud kasutada ka näiteks kolme sõnastikku, kus ühes on matriklinumbrile vastav eesnimi, teises perekonnanimi, kolmandas isikukood. Ühendades kõik ühte isendisse muutub kood palju kergemini loetavaks ja hallatavamaks.
Näidiskoodi lõppkuju:
class Õppija: kool = "Tartu Ülikool" def kusÕpib(): return "Õpib koolis " + Õppija.kool + "." def __init__(self, matrikli_nr, eesnimi, perenimi, isikukood): self.matrikli_nr = matrikli_nr self.eesnimi = eesnimi self.perenimi = perenimi self.isikukood = isikukood def __str__(self): return "Õppija nimi: "+self.eesnimi + " " + self.perenimi + ".\n" + Õppija.kusÕpib() def arvuta(a, b): return a + b def sugu(self): if self.isikukood//10000000000%2 == 1: return "M" else: return "N" # Konstruktori kasutus õppur1 = Õppija("A034", "Albert", "Paas", 34105212737) õppur2 = Õppija("A037", "Pearu", "Murakas", 34206122154) # Isendimuutuja tekitamine/muutmine õppur1.lemmikloom = "koer" print(õppur1.lemmikloom)
# Isendispetsiifiline funktsioon print(õppur1.sugu())
# Väljastamine, kasutades isendispetsiifilist funktsiooni koos staatilise muutuja ja funktsiooniga print(õppur1) print(õppur2)
# Muu staatilise funktsiooni kasutus print(Õppija.arvuta(1, 2))
# Isendid järjendis õppurid = [õppur1, õppur2] for el in õppurid: print(el.eesnimi, el.perenimi + ", isikukood:", el.isikukood)