!!6. Objektorienteeritud programmeerimine
Siiamaani oleme salvestanud andmeid erinevatesse andmestruktuuridesse: järjendid, ennikud, hulgad, sõnastikud. Vaatamata on veel jäänud vaieldamata kõige võimsam andmestruktuur üldse. See on andmestruktuur, millega luuakse teisi andmestruktuure. See on andmestruktuur, mida ei saa võibolla isegi kutsuda andmestruktuuriks, sest see on omaette klass. 

Jutt käib justnimelt klassidest ja nendega loodavatest objektidest. Tegu on objektorienteeritud programmeerimise peatükiga. Vaatame, kuidas oleme juba varem klassidega kokku puutunud, kuidas luua enda klasse, mis nendega teha saab ning kus neid hästi rakendada saab. Proovime luua enda versiooni mõnest populaarsest rakendusest. 

!!!Ettevalmistus
Selle peatüki läbimiseks piisab Pythoni installatsioonist: midagi paigaldama ei pea. 

Et peatükist aru saada, peab olema läbitud õpiku [[https://progeopik.cs.ut.ee/osa1.html | esimene osa]]. 

!!!Tuttavate objektide uurimine
Kuigi siin kursusel pole veel klasside telgitagustesse vaadatud, oleme mõningaid klasse kasutanud. Tuletame meelde @@datetime@@ moodulit. See on moodul, mis lubab salvestada ajahetki, sh kuupäevi ja kellaaegu. 

Mooduli importimine:

(:codestart python gutter='false':)
>>> import datetime
(:codeend:)

Moodulis @@datetime@@ asub klass nimega datetime. Klassidega saab teha isendeid ehk objekte. Need on muutujad, mille küljes võib olla teisi muutujaid ehk atribuute. Need muutujad võivad olla ka funktsioonid, mida kutsutakse meetoditeks. Et luua uut objekti, peab klassi välja kutsuma kui funktsiooni, mis tagastab selle klassi isendi. Proovime luua @@datetime@@ isendi. Funktsiooni parameetriteks lähevad aastaaeg, kuu, päev, tund, minut, sekund ja mikrosekund.

(:codestart python gutter='false':)
>>> kuupäev = datetime.datetime(2020, 2, 29, 12, 34, 56, 789)
(:codeend:)

Muutujasse @@kuupäev@@ on nüüd salvestatud klassi datetime isend ehk objekt. Uurime, mis on selle muutujatüüp (@@type()@@) ning mis muutujad sellega seostuvad (@@dir()@@).

(:codestart python gutter='false':)
>>> type(kuupäev)
<class 'datetime.datetime'>
>>> dir(kuupäev)
['__add__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__radd__', '__reduce__', '__reduce_ex__', '__repr__', '__rsub__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', 'astimezone', 'combine', 'ctime', 'date', 'day', 'dst', 'fold', 'fromisocalendar', 'fromisoformat', 'fromordinal', 'fromtimestamp', 'hour', 'isocalendar', 'isoformat', 'isoweekday', 'max', 'microsecond', 'min', 'minute', 'month', 'now', 'replace', 'resolution', 'second', 'strftime', 'strptime', 'time', 'timestamp', 'timetuple', 'timetz', 'today', 'toordinal', 'tzinfo', 'tzname', 'utcfromtimestamp', 'utcnow', 'utcoffset', 'utctimetuple', 'weekday', 'year']
(:codeend:)

Näeme, et tegemist on tõepoolest isendiga klassist @@datetime.datetime@@, millel on palju seotud muutujaid ehk välju. Näiteks on seal väljad @@year@@, @@month@@, @@day@@, @@hour@@, @@minute@@, @@second@@ ja @@microsecond@@, millega saame kätte arvud, millega me isendi lõime. Neid välju nimetatakse '''isendiväljadeks'''.

(:codestart python gutter='false':)
>>> kuupäev.year
2020
>>> kuupäev.month
2
>>> kuupäev.microsecond
789
(:codeend:)

Klassidel saab tihti isendiväljade väärtusi ise määrata (nt @@kuupäev.year = 2021@@), aga datetime seda ei luba. 

Loodud isendil on ka funktsioone, mis kasutavad mõnda isendivälja. Neid nimetatakse '''isendimeetoditeks'''. Näiteks on üks selline @@strftime()@@, mis tagastab kuupäeva sõnena soovitud formaadis. 

(:codestart python gutter='false':)
>>> kuupäev.strftime
<built-in method strftime of datetime.datetime object at 0x7f279e256570>
>>> kuupäev.strftime("%Y-%m-%d %H:%M:%S")
'2020-02-29 12:34:56'
(:codeend:)

Klassidel on tihti isendimeetodeid, mis muudavad isendivälju, aga @@datetime@@ klassil selliseid pole. 

Klassidel on ka funktsioone, mis ei vaja isendeid ega isendivälju. Nendega saab üldiselt teha midagi selle klassiga seotut, näiteks sama klassi isendi tagastamist. Selliseid funktsioone kutsutakse '''staatilisteks meetoditeks'''. Sisuliselt on need lihtsalt funktsioonid, mis on klassi küljes. Klassil @@datetime@@ on näiteks staatiline meetod @@now()@@, mis tagastab praeguse ajahetke jaoks @@datetime@@ isendi.

(:codestart python gutter='false':)
>>> datetime.datetime.now()
datetime.datetime(2020, 2, 29, 18, 20, 33, 30651)
(:codeend:)

Selliseid meetodeid saab ka välja kutsuda isendite kaudu, aga üldiselt nii ei tehta. 

(:codestart python gutter='false':)
>>> kuupäev.now()
datetime.datetime(2020, 2, 29, 18, 20, 33, 30652)
>>> datetime.datetime.now().now().now().now()
datetime.datetime(2020, 2, 29, 18, 20, 33, 30653)
(:codeend:)

Klassidel võib ka olla staatilisi välju, mis on kõikidel isenditel samad. Klassil @@datetime@@ on näiteks staatilised väljad @@min@@ ja @@max@@, mis näitavad minimaalseid ja maksimaalseid kuupäeva väärtuseid.

(:codestart python gutter='false':)
>>> datetime.datetime.min
datetime.datetime(1, 1, 1, 0, 0)
>>> datetime.datetime.max
datetime.datetime(9999, 12, 31, 23, 59, 59, 999999)
(:codeend:)

Väljade ja meetodite selgituste saamiseks saab kasutada [[https://docs.python.org/3/library/datetime.html#datetime.datetime | dokumentatsiooni]] ja @@help()@@ funktsiooni.

(:codestart python gutter='false':)
>>> help(datetime.datetime)
Help on class datetime in module datetime:

class datetime(date)
 |  datetime(year, month, day[, hour[, minute[,second[,microsecond[,tzinfo]]]]])
 |  
 |  The year, month and day arguments are required. tzinfo may be None, or an
 |  instance of a tzinfo subclass. The remaining arguments may be ints.
 |  
 |  Method resolution order:
 |          datetime
 |          date
 |          builtins.object
 |  
 |  Methods defined here:
 |  
 |  __add__(self, value, /)
 |          Return self+value.
 | ...
(:codeend:)

Järgmisena vaatame, kuidas klasse luua ning kuidas need töötavad. 

!!!Klasside loomine
Loome enda kuupäeva klassi. Et vähendada tööd, mõtleme ainult kuupäevadele ilma kellaaegadeta ehk meie kuupäeva klassil peaks olema 3 isendivälja: @@aasta@@, @@kuu@@ ja @@päev@@. Meie ideaalses maailmas on kalendris igal kuul täpselt 31 päeva, muidu peaks kontrollima kuu päevade arvu vastavalt kuule. 

Klassidele on tavaks panna suure algustähega nimi, seega paneme selle nimeks @@Kuupäev@@. Klasse saab luua @@class@@ käsuga. Selle käsu alla saab defineerida meetodeid ja välju. Lisame alguses ainult @@aasta@@ välja ning määrame selle väärtuseks @@2020@@. 

(:codestart python gutter='false':)
class Kuupäev:
    aasta = 2020
(:codeend:)

Nüüd saame luua selle klassi isendi. Selleks kutsume seda välja kui funktsiooni. Täpselt nagu tavaliste muutujatega, saame isendivälju kasutada ning üle kirjutada. Saame ka uusi välju omistada ning neid kasutada. 

(:codestart python gutter='false':)
>>> kuupäev = Kuupäev()
>>> kuupäev
<__main__.Kuupäev object at 0x7fa1cf208e80>
>>> kuupäev.aasta
2020
>>> kuupäev.aasta = 2021
>>> kuupäev.aasta
2021
>>> kuupäev.kuu = 2
>>> kuupäev.kuu
2
(:codeend:)

!!!!Konstruktorid
Praegu on igal uuel loodud isendil @@aasta@@ väärtus automaatselt @@2020@@. See ei ole väga ettenägelik lähenemine, sest sellest järgnevatel aastatel ei ole see enam kasulik ja iga uue isendi loomisel peab selle üle kirjutama. Samuti on tüütu isendi loomisel manuaalselt teisi muutujaid määrata. 

Kui me lõime uue datetime isendi, kutsusime me klassi välja nagu funktsiooni ja lisasime muutujate väärtused parameetritena.

(:codestart python gutter='false':)
>>> kuupäev = datetime.datetime(2020, 2, 29, 12, 34, 56, 789)
(:codeend:)

Et sellist võimalust enda klassile lisada, peame defineerima erilise funktsiooni, mida kutsutakse '''konstruktoriks'''. Selle funktsiooni nimi Pythonis on @@__init__@@ ning selle esimene parameeter peab olema @@self@@, mille väärtuseks läheb alati käesolev isend. Järgnevad parameetrid võivad olla ise määratud. Need on need parameetrid, mis lähevad funktsiooni sulgudesse selle väljakutsumisel. 

(:codestart python gutter='false':)
class Kuupäev:
    def __init__(self, aasta, kuu, päev):
        self.aasta = aasta
        self.kuu = kuu
        self.päev = päev
(:codeend:)

Defineerisime klassile @@Kuupäev@@ konstruktori kolme parameetriga: @@aasta@@, @@kuu@@ ja @@päev@@. Kuna @@self@@ tähendab käesolevat isendit, siis määrame uuele isendile uued isendiväljad, mille väärtused võtame funktsiooni parameetritelt samamoodi, nagu me enne omistasime isendile välju. 

Nüüd saame luua uue isendi ise valitud algväärtustega.

(:codestart python gutter='false':)
>>> kuupäev = Kuupäev()
TypeError: __init__() missing 3 required positional arguments: 'aasta', 'kuu', and 'päev'
>>> kuupäev = Kuupäev(2020, 2, 29)
>>> kuupäev.aasta
2020
>>> kuupäev.kuu
2
>>> kuupäev.päev
29
(:codeend:)

Konstruktoris saame ka kontrollida väärtuseid. Kui kuu või päeva väärtus on sobivatest väärtustest väljaspool, viskame erindi. Meie klassil on kõikidel kuudel on teadagi 31 päeva.

(:codestart python gutter='false':)
class Kuupäev:
    def __init__(self, aasta, kuu, päev):
        self.aasta = aasta
            
        if 1 <= kuu <= 12:
            self.kuu = kuu
        else:
            raise ValueError("Kuu peab olema 1 kuni 12.")
    
        if 1 <= päev <= 31:
            self.päev = päev
        else:
            raise ValueError("Päev peab olema 1 kuni 31.")
(:codeend:)

(:codestart python gutter='false':)
>>> kuupäev = Kuupäev(2020, 13, 1)
ValueError: Kuu peab olema 1 kuni 12.
>>> kuupäev = Kuupäev(2020, 2, -3)
ValueError: Päev peab olema 1 kuni 31.
>>> kuupäev = Kuupäev(2020, 2, 29)
>>> 
(:codeend:)

!!!!Isendimeetodid
Kui me tahame praegu kuupäeva isendit teisendada sõneks, siis peame kokku liitma 3 isendivälja. 

(:codestart python gutter='false':)
>>> kuupäev = Kuupäev(2020, 2, 29)
>>> "{}-{:02d}-{:02d}".format(kuupäev.aasta, kuupäev.kuu, kuupäev.päev)
'2020-02-29'
(:codeend:)

Võime lisada klassile isendimeetodi, mis seda ise teeb ning tagastab kuupäeva sõnevormingus. Asendame lihtsalt isendi viite muutujaga @@self@@.

(:codestart python gutter='false':)
    def sõnena(self):
        return "{}-{:02d}-{:02d}".format(self.aasta, self.kuu, self.päev)
(:codeend:)

(:codestart python gutter='false':)
>>> kuupäev = Kuupäev(2020, 2, 29)
>>> kuupäev.sõnena()
'2020-02-29'
(:codeend:)

Tegelikult peaks igal klassil olema selline meetod, mis väljendab isendit sõnena. Kui proovime isendit praegu sõneks teisendada, siis antakse meile klassi nimi ja mäluviide, aga isendi sisu ei näidata. 

(:codestart python gutter='false':)
>>> str(kuupäev)
'<__main__.Kuupäev object at 0x7feca1b2cc70>'
>>> kuupäev
<__main__.Kuupäev object at 0x7feca1b2cc70>
(:codeend:)

Kui aga @@datetime@@ klassi isendit sõneks teisendada, siis tuleb ülevaade isendiväljadest.

(:codestart python gutter='false':)
>>> kuupäev = datetime.datetime(2020, 2, 29, 12, 34, 56, 789)
>>> str(kuupäev)
'2020-02-29 12:34:56.000789'
>>> kuupäev
datetime.datetime(2020, 2, 29, 12, 34, 56, 789)
(:codeend:)

See, mida sõneks teisendamisel tehakse, oleneb jälle ühest erilisest meetodist nimega @@__str__@@. Kui sõneks teisendatakse, võetakse selle meetodi tagastus. Kirjutame oma kuupäeva klassile selle meetodi. Võime kasutada sõnena meetodit selle tagastuses.

(:codestart python gutter='false':)
    def __str__(self):
        return self.sõnena()
(:codeend:)

(:codestart python gutter='false':)
>>> kuupäev = Kuupäev(2020, 2, 29)
>>> str(kuupäev)
'2020-02-29''
>>> kuupäev.__str__()
'2020-02-29'
>>> kuupäev
<__main__.Kuupäev object at 0x7f7f85ea5c70>
(:codeend:)

Sõneks teisendamine töötab hästi, aga niisama isendi kirjutamine tagastab jälle klassi nime ja mäluviite. See on sellepärast, et see väärtus võetakse teisest meetodist nimega @@__repr__@@ (tuleb sõnast ''representation''). Saame enda klassile selle meetodi kirjutada. Tavaks on sellel meetodil tagastada sõne, mille abil saab konstruktorit välja kutsuda, et saada selline isend.

(:codestart python gutter='false':)
    def __repr__(self):
        return "Kuupäev({}, {}, {})".format(self.aasta, self.kuu, self.päev)
(:codeend:)

(:codestart python gutter='false':)
>>> kuupäev = Kuupäev(2020, 2, 29)
>>> kuupäev
Kuupäev(2020, 2, 29)
>>> repr(kuupäev)
'Kuupäev(2020, 2, 29)'
(:codeend:)

Lisame veel isendimeetodi, mis muudab isendiväljade väärtuseid. Näiteks võiks olla meetod aastate juurde lisamiseks.

(:codestart python gutter='false':)
    def lisa_aastaid(self, aastate_arv):
        self.aasta += aastate_arv
(:codeend:)

(:codestart python gutter='false':)
>>> kuupäev = Kuupäev(2020, 2, 29)
>>> kuupäev.lisa_aastaid(5)
>>> kuupäev
Kuupäev(2025, 2, 29)
(:codeend:)

Kuude lisamine on keerulisem, sest kui kuu väärtus on üle 12, siis peab vastavalt aastaid juurde lisama.

(:codestart python gutter='false':)
    def lisa_kuid(self, kuude_arv):
        self.kuu += kuude_arv
        if self.kuu > 12:
            # Lisame aastaid juurde nii palju, kui neid 12-st üle läheb
            self.lisa_aastaid((self.kuu - 1) // 12)
            # Uus kuude arv on praeguse arvu jääk jagamisel 12-ga
            self.kuu = (self.kuu - 1) % 12 + 1
(:codeend:)

(:codestart python gutter='false':)
>>> kuupäev = Kuupäev(2020, 2, 29)
>>> kuupäev.lisa_kuid(13)
>>> kuupäev
Kuupäev(2021, 3, 29)
>>> kuupäev.lisa_kuid(120)
>>> kuupäev
Kuupäev(2031, 3, 29)
(:codeend:)

Proovi ise lisada päevade lisamise meetod. Kui kasutada @@lisa_kuid@@ meetodit, siis ei pea aastaid eraldi juurde lisama. Lihtsuse mõttes võib ikka eeldada, et igal kuul on 31 päeva.

Kui kõik kolm meetodit on tehtud, saab ka teha ühise liitmismeetodi.

(:codestart python gutter='false':)
    def lisa(self, aastate_arv=0, kuude_arv=0, päevade_arv=0):
        self.lisa_aastaid(aastate_arv)
        self.lisa_kuid(kuude_arv)
        self.lisa_päevi(päevade_arv)
(:codeend:)

(:codestart python gutter='false':)
>>> kuupäev = Kuupäev(2020, 2, 29)
>>> kuupäev.lisa(aastate_arv=1, kuude_arv=2, päevade_arv=3)
>>> kuupäev
Kuupäev(2021, 5, 1)
(:codeend:)

!!!!Staatilised meetodid
Lisame oma klassile ühe staatilise meetodi. Staatiliste meetodite puhul peab lihtsalt parameetri @@self@@ ära jätma, sest ei kasutata ühtegi isendivälja ega -meetodit. Kirjutame meetodi, mis tagastab isendi praeguse kuupäeva väärtustega. Võtame väärtused @@datetime@@ isendist. 

(:codestart python gutter='false':)
    def praegu():
        nüüd = datetime.datetime.now()
        return Kuupäev(nüüd.year, nüüd.month, nüüd.day)
(:codeend:)

(:codestart python gutter='false':)
>>> Kuupäev.praegu()
Kuupäev(2020, 2, 29)
(:codeend:)

Proovi kirjutada staatiline meetod parsi, mis võtab parameetrina sõne formaadis "YYYY-MM-DD", nt "2020-02-29" ning tagastab uue kuupäeva isendi nende väärtustega.

(:codestart python gutter='false':)
>>> Kuupäev.parsi("2020-02-29")
Kuupäev(2020, 2, 29)
(:codeend:)

!!!Kokkuvõte
Vaatasime natuke Pythoni objektorienteeritud programmeerimise võimalusi: mis asjad on klassid ja isendid, kuidas neid luua ja kasutada ning mis on, isendiväljad ja meetodid. Mõned võimalused jäid vaatamata, näiteks privaatsed väljad, klasside pärilus, itereeritavad isendid, objektide käitumine liitmisel, lahutamisel, võrdlemisel jne. Nendega saab tutvuda [[https://docs.python.org/3/tutorial/classes.html#inheritance | Pythoni dokumentatsioonis]].

Programmeerimise paradigmat, mis kasutab objekte, nimetatakse objektorienteeritud programmeerimiseks. See on väga levinud viis, kuidas programme kirjutada ning sellest räägitakse rohkem aines "Objektorienteeritud programmeerimine" ([[https://ois2.ut.ee/#/courses/LTAT.03.003/details | LTAT.03.003]]), kus kasutatakse Java keelt. Kui on plaanis edaspidi Pythoniga tegeleda, siis on Pythoni objektorienteerituse tundmine väga kasulik.

!!!Enesekontrolliküsimused
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring6_1.html width=100% height="230px" border="0" :)
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring6_2.html width=100% height="230px" border="0" :)
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring6_3.html width=100% height="230px" border="0" :)

!!!Ülesanded
1. Lõpeta kuupäeva klass. Peavad olema realiseeritud meetodid @@lisa_päevi@@ ja @@parsi@@. Meetodid peavad arvestama päevade arvuga kuudes: jaanuaris on 31, veebruaris on 28-29, märtsis on 31, aprillis on 30 jne. Veebruari päevade arv sõltub aastast: kui aasta jagub arvuga 4, siis on 29, välja arvatud siis, kui see jagub arvuga 100, siis on 28, välja arvatud siis, kui see jagub arvuga 400, siis on jälle 29.Aastal 1900 oli veebruaris 28 päeva, aastal 2000 oli veebruaris 29 päeva. 

2. Mõtle välja klass, mis võib sulle kasuks tulla ning kirjuta programm selle klassi defineerimise ja kasutusnäidetega. Klassil peab olema konstruktor, vähemalt üks isendiväli, vähemalt üks isendimeetod ning meetodid @@__str__@@ ja @@__repr__@@.

Mõned ideed: 

* Tabeli klass, kuhu saab lisada ridu ja veerge. Meetodid võiksid olla rea ja veeru kätte saamiseks indeksitega. Tabelit võiks saada teisendada CSV-vormingusse.
* Koordinaadi klass, millel on x- ja y-koordinaatide väärtuste isendiväljad. Meetod tagastab teise koordinaadi objekti kauguse.
* Kujundi klass (näiteks kolmnurk, ristkülik või ring), millel on külgede pikkused ja nurkade suurused. Meetodid pindala ja ümbermõõtude arvutamise jaoks. Võiks ka olla meetod, mis joonistab selle kujundi mooduliga @@turtle@@.
