Loeng 12: Objektorienteeritud programmeerimine (OOP), klass, konstruktor ja spetsiaalne meetod __init__
, objekt, meetodi defineerimine, klassi staatiline muutuja, spetsiaalne meetod, pärilikkus, plümorfism, lausend: class
, muutuja self
, funktsioonid: isinstance
, issubclass
, jm.¶
Koodinäited¶
Objektorjenteeritud programmeerimine (OOP)¶
Milleks meile seda veel vaja?¶
Osa teist on enda teadmata juba programmeerinud kasutades OOPi. Pythonis on kõik andmetüübi esindajad OOP objektid. Pythoni andmetüübid on defineertud just klassides. Me oleme selle ideega, et andmetüüp = klass, juba põgusalt kohtunud. Meie jaoks on andmetüüpi nimi olnud klassi nimi. Teatavasti saab funktsiooniga type
järgi vaadata objekti andmetüüpi:
print(type([]))
print(type (1))
print(type(True))
print(type(1.2))
print(type(None))
print(type(print))
print(type(type))
<class 'list'> <class 'int'> <class 'bool'> <class 'float'> <class 'NoneType'> <class 'builtin_function_or_method'> <class 'type'>
Siiamaani oleme hakkama saanud kasutades Pythonit rohkem lihtsa skriptimiskeelena ja tuginedes funktsionaalprogrameerimise võtetele. Milleks on meile vajalik OOP?
- Nagu just mainitud on Python ülesse ehitatud objektorjeteerituse baasil. Seega selleks, et teada kuidas keelt paremini ja oskuslikumalt kasutada võiksime OOPi teada.
- Meile teadaolevad võtted pole piisavalt võimekad ega mugavad isegi suhteliselt lihtsate probleemide lahendamiseks.
Näide: TalTechi tudengite andmetetega töötlemine. Tudengite andmete näide on näidatud Tabelis 1.
Tudeng | Tudeng 1 | Tudeng 2 | ... | Tudeng n |
---|---|---|---|---|
Nimi | Juulius Tipikas | Juulia Tipikas | ... | Mari Karu |
Sünniaeg | 1. sept 1918 | 2. sept 1922 | ... | 2. sept 2022 |
Isikukood | 982674752 | 182374658 | ... | 8347542934 |
Telefon | 112 | 6207777 | ... | 4207667 |
Hinded | - | 4, 5, 5 | ... | 5, 5, 5 |
Teatavasti on Taltechi tudengite arv n tuhandetes.
Lahendus 1: Rumal lahendus.
# Loome kõigi tudengite andmeid sisaldavad listid.
tudeng1 = ['Juulius Tipikas', '1. sept 1918', 982674752, 112, []]
tudeng2 = ['Juulia Tipikas', '2. sept 1922', 182374658, 6207777, [4, 5, 5]]
# Püüame andmeid organiseerida.
Tudengid = [tudeng1, tudeng2]
# Nime järgivaatamine. Olen huvitatud Juuliusest.
print(Tudengid[0][0])
# Lisame Juuliusele hinde.
Tudengid[0][4].append(5)
# Hinnete järgivaatamine.
print(Tudengid[0][4])
Juulius Tipikas [5]
Siia on suhteliselt lihtne andmeid, näiteks konkreetse tudengi uut hinnet, sisestada. Mis saab aga siis kui Andmekaitse Inspektsioon leiab, et sina ei tohi tudengite telefoninumbreid säärasel kujul salvestada ja sa pean kõik need numbrid kustutama:
# Kuidagi peab kõigi tudengite listid läbi käima.
del tudeng1[-2]
del tudeng2[-2]
print(Tudengid[0]) # Telefoninumber kustutatud.
print(Tudengid[1]) # Telefoninumber kustutatud.
['Juulius Tipikas', '1. sept 1918', 982674752, [5]] ['Juulia Tipikas', '2. sept 1922', 182374658, [4, 5, 5]]
Lahendus 2: Natukene targem lahendus, kuid siiski suhteliselt ebamugav.
# Koondame tudengite andmed sõnaraamtusse.
tudeng1 = {
'nimi': 'Juulius Tipikas',
'sünnipäev': '1. sept 1918',
'isikukood': 982674752,
'telefon': 112,
'hinded': []
}
tudeng2 = {
'nimi': 'Juulia Tipikas',
'sünnipäev': '2. sept 1922',
'isikukood': 182374658,
'telefon': 6207777,
'hinded': [4, 5, 5]
}
# Püüame andmeid organiseerida.
Tudengid = {
'tudeng1': tudeng1,
'tudeng2': tudeng2
}
# Nime järgivaatamine. Olen huvitatud Juuliusest.
print(Tudengid['tudeng1']['nimi'])
# Lisame Juuliusele hinde.
Tudengid['tudeng1']['hinded'].append(5)
# Hinnete järgivaatamine.
print(Tudengid['tudeng1']['hinded'])
Juulius Tipikas [5]
Siia on suhteliselt lihtne andmeid, näiteks konkreetse tudengi uut hinnet, sisestada. Mis saab aga siis kui Andmekaitse Inspektsioon leiab, et sina ei tohi tudengite telefoninumbreid säärasel kujul salvestada ja sa pean kõik need numbrid kustutama:
# Kuidagi peab kõigi tudengite sõnaraamatud läbi käima.
del tudeng1['telefon']
del tudeng2['telefon']
print(Tudengid['tudeng1']) # Telefoninumber kustutatud.
print(Tudengid['tudeng2']) # Telefoninumber kustutatud.
{'nimi': 'Juulius Tipikas', 'sünnipäev': '1. sept 1918', 'isikukood': 982674752, 'hinded': [5]} {'nimi': 'Juulia Tipikas', 'sünnipäev': '2. sept 1922', 'isikukood': 182374658, 'hinded': [4, 5, 5]}
Eelmainitud näidet saab lahendada paremini just kasutades OOPi. OOP organiseerib andmed automaatselt, lihtsustab andmete sisestamist ja haldamist. OOP lubab üksikasjalikult defineerida andmete struktuuri (nt. vt. Tabel 1 esimene veerg) mida saab omakorda kasutada konkreetsete objektide ja nendega seotud andmete haldamiseks (nt. vt. Tabel 1 esimeses reas olevad tudengid).
Python ja OOP¶
Joonis 1. Programmeerimine Pythonis. Programm ja selle osad. |
Väljavõte Loengust 1 vt. Slaidid 1:
Python on väga kõrge tasemega üldotstarbeline dünaamiline objektorienteeritud skriptimiskeel. Python võimaldavad kasutada erinevaid programmeerimise paradigmasid nagu funktsionaalseid, protseduraalseid, ja objektorienteeritud programmeerimise võtteid, vrd. Joonis 1.
Funktsionaalne programmeerimine: funktsiooni rakendamisel argumendile (sisendile) saadakse väärtus. Funktsioonid on taaskasutatavad.
Protseduraalne programmeerimine: programmi poolt tehtavad arvutused ja tegevused on koondatud nn. protseduuri blokkidesse, mida saab taaskasutada.
Objektorienteeritud programmeerimine (OOP): programmi töös manipuleeritakse objektidega, mis on spetsiaalsed andmestruktuurid objekti andmetest (argumentidest, muutujatest) ja objektiga seotud tegevustest (meetodid). Objekti võib käsitleda kui iseseisvat üksust, mis oskab andmeid vastu võtta, neid töödelda, salvestada ja väljastada.
OOP programmeerimise paradigma¶
OOPis opereerime objektidega ja klassidega lisaks muutujatele, avaldistele, erinevatele konstruktsioonidele, funktsioonidele ja protseduuridele. OOPis käsiteme objekte kui iseseisvaid üksusi mis oskavad andmeid vastu võtta, neid töödelda, salvestada ja väljastada. Objektid defineeritakse ja luuakse Pythoni konstruktsioonis nimega klass (lausend class
).
Klass (class
) -- objekti loomise eeskiri millega käivad kaasas objekti kirjeldavad atribuudid (andmed, muutujad) ja objektiga seotud tegevused (meetodid, spetsiaalsed meetodid, jne.). Eeskiri milla alusel luuakse objekte.
OOP programmeerija maailmapilt 1¶
- Maailm koosneb:
- Füüsilistest ja mõttelistest asjadest (mõtle: objekt)
- Näiteks: autod, inimesed, mõisted
- Tegevustest asjadega
- Näiteks: auto käivitamine
- Asjade vahelistest suhetest
- Näiteks: liikluseeskirjad reguleerivad autode omavahelist liiklemist, füüsikaseadused määravad auto ja tee vahelise kontakti
- Füüsilistest ja mõttelistest asjadest (mõtle: objekt)
- Asju saab liigitada:
- Autod on sõidukid
- Veoauto ja sõiduauto on auto
- Jalgratas ei ole auto aga on sõiduk
- Inimesed ja koerad on elusolendid
- Asjadel on omadused:
- Auto värvus, võimsus, rataste arv, asukoht, kiirus
- Asjad koosnevad teistest asjadest:
- Auto koosneb mootorist, ratastest, roolist, jne.
- Liigid moodustavad pärinevuse puu:
- Päritakse omadusi ja tegevusi
- Sõiduauto $\rightarrow$ auto $\rightarrow$ mootorsõiduk $\rightarrow$ ... $\rightarrow$ füüsiline objekt $\rightarrow$ ... $\rightarrow$ element $\rightarrow$ molekul $\rightarrow$ aatom $\rightarrow$ elementaarosakesed
- Tegevused muudavad asjade omadusi:
- Auto kiiruse muutmine
- Asjad suhtlevad või vahetavad informatsiooni omavahel:
- Inimene juhib autot
- Asjade omadused ja tegevused on määratud asjade liigiga
- Asjad on enda liigi esindajad
OOP programmeerija maailmapilt 2¶
- Maailm programmeerimise terminites:
- Objekt esindab asja (object, class instance)
- Klass esindab asja liiki
- Funktsioon esindab üldist tegevust asjadega
- Meetod esindab liigiga seotud tegevust asjadega
- Atribuut/muutuja esindab asja omadust
- Liides (interface) määrab asjade vahelise suhtluse viisi (ei käsitle selles kursuses)
- Programm on maailma mudel:
- Töötav programm simuleerib maailmas toimuvat
- Töötav programm võib muuta maailma
- Maailm võib muuta programmi tööd
- Programmeerimine on maailma mudeli kirjeldamine
Klassi ja objekti loomine¶
Klass milles pole meetodeid¶
Klassi defineerimiseks kasutame lausendit class
. Klassi sisu eristame muust koodist taandega nii nagu ikka Pythoni blokkides. Klassi nimesid on soovitatav kirjutda suure algustähega nt. Foo
. Nimes esinevaid sõnu eristame omakorda suure algustähega ja kirjutades klassinime kokku, nt. FooBar
mitte Foo_Bar
, vmt. Koodistiili ja dokumenteerimise kohat loe lisaks PEP 8 ja PEP 257.
Lihtne klass mis sisaldab nn. staatilisi klassimuutujaid. Klassimuutujaite skoop on globaalne. Muutujatele viitame kasutades klassi või objekti nime:
class A:
a = 1 # Klassi staatilised muutujad või atribuudid.
b = 2
print(A.a) # Klassimuutuja väljakutse, sulge pole.
print(A.b)
print(A)
1 2 <class '__main__.A'>
Objekti loomine kasutades eelmises koodirakus defineeritud klassi A
. Üks klass lubab luua meelevaldse arvu objekte. Objekte nimetatakse ka klassi isendiks või instantsiks.
# Objekti loomine, class instance.
obj = A() # NB! sulud.
print(obj.a) # Klassimuutuja väljakutse kasutades objekti. Sulge pole.
print(obj.b)
print(obj)
print(type(obj)) # Mis andmetüüp? Klass = andmetüüp.
1 2 <__main__.A object at 0x12a890f10> <class '__main__.A'>
Real 2 oleva objekti defineerimiseks vajalik koos on kodeeria eest varjatud. Python teostas automaatselt kõik vajaliku objekti loomiseks.
Konstruktor¶
Objektidega seotud andmete, nende struktuuri, organisatsiooni ning algse oleku ehk initsialiseeringu saame valida ise. Iga klassi abil loodud objektiga käivad kaasas selle enda andmed. Initsialiseeringu defineerimiseks kasutame klassibloki sees spetsiaalset meetodit ehk funktsiooni nimega __init__
. Spetsiaalset meetodit __init__
nimetatakse ka maagiliseks meetodiks. Spetsiaalne meetod __init__
omab kohustuslikku positsionaalset muutujat, millele pannakse tavaliselt nimeks self
. Muutujaga self
tähistame objekti mida hiljem luuakse kasutades kirjutatavat klassi, vt. Joonis 2 ja järgmist lõiku.
Näide: Auto ja selle mudel.
class Auto:
def __init__(self, mudel): # Konstruktor, toetab kõiki argumenditüüpe.
# Siia kirjutada kõik vajaliku objekti defineerimiseks...
self.mudel = mudel # Objekti muutuja/atribuut, instance variable.
auto = Auto('BMW') # Loon objekti "auto" kasutades klassi Auto, class instance.
print(auto.mudel)
BMW
Joonis 2. Kostruktori __init__ esimene positsionaalne argument, millele pannakse tavaliselt nimeks self , ja selle tähendus. |
Klass koos meetoditega¶
Selleks, et meetodid siduda kindla objekti initsialiseeringuga kasutame ka nendes spetsiaalset argumenti või muutujat self
. Joonis 3 näitab kuidas mõelda spetsiaalsest muutujast self
tavaliste meetodite kontekstis.
Näide: Auto ja selle asukoht.
class Auto:
def __init__(self, kiirus = 0): # Konstruktor, toetab kõiki argumenditüüpe.
self.kiirus = kiirus # Objekti muutuja/atribuut, instance variable.
def set_kiirus(self, kiirus): # Meetod 1.
self.kiirus = kiirus # Kirjutan väärtuse üle.
def get_asukoht(self, aeg): # Meetod 2
asukoht = self.kiirus * aeg
print('Auto asub', asukoht, 'km kaugusel.')
auto = Auto() # Loon objekti "auto" kasutades klassi Auto.
auto.set_kiirus(60) # Kiirus 0 on igav.
auto.get_asukoht(0.5)
Auto asub 30.0 km kaugusel.
Joonis 3. Kostruktori __init__ esimene positsionaalne argument, millele pannakse tavaliselt nimeks self , ja selle tähendus ning kasutus meetodites. |
Loon teise objekti nimega teine_auto
kasutades sama klassi Auto
.
teine_auto = Auto()
teine_auto.set_kiirus(100)
teine_auto.get_asukoht(2.0)
Auto asub 200.0 km kaugusel.
Lisame klassi Auto
definitsioonile ka staatilise klassimuutuja:
class Auto:
värv = 'kollane' # Klassimuutuja, static variable.
def __init__(self, kiirus = 0): # Konstruktor.
self.kiirus = kiirus # Objekti muutuja.
def set_kiirus(self, kiirus):
self.kiirus = kiirus
def get_asukoht(self, aeg):
asukoht = self.kiirus * aeg
print('Auto asub', asukoht, 'km kaugusel.')
auto = Auto() # Loon auto kasutades klassi Auto, class instance.
print(auto.värv) # Läbi objekti.
print(Auto.värv) # Läbi klassi.
auto.set_kiirus(60)
auto.get_asukoht(0.5)
kollane kollane Auto asub 30.0 km kaugusel.
Pärilikkus (inheritance), baas- või superklass ja alamklass¶
Staatiliste muutujate pärimine enda loodud superklassist¶
Klassi defineerimisel saame taaskasutada juba olemasolevate klasside (kogu) sisu. Sellist Pythoni klasside omadust nimetame pärilikkuseks. Sellist koodi taaskasutust nimetame pärimiseks. Klassi mis pärandab enda sisu nimetame baas- ehk superklassiks ja klassi mis pärib sisu nimetame alamklassiks. Pärilikkus võimaldab koodi kirjutada kiiremini ja lisada objektidele funktsionaalsust ilma olemasolevatea klasside muutmist.
Pärida saab mitmest klassist korraga (järgmise nädala loeng). Klassid saavad pärida teiste klasside staatilisi muutujaid, initsialiseeringuid (järgmise nädala loeng) ja meetodeid. Lihtne näide kus pärime klassimuutujaid:
class A: # Baas- ehk superklass.
a = 1 # Klassi staatilised muutujad.
b = 2
c = 3
class B(A): # Alamklass B pärib superklassist A.
c = 33 # Klassimuutuja, kõik muu päritakse ülalt.
obj = B()
print(obj.a, obj.b, obj.c)
print(dir(obj)) # Sisaldab päritud andmeid a, b.
1 2 33 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a', 'b', 'c']
Meetodite pärimine enda loodud superklassist¶
Sarnaselt statilistele muutujatele saame pärida terveid meetodeid:
class Tudeng: # Baas- ehk superklass.
def tereta(self):
print("Tere!")
class Keemik(Tudeng): # Alamklass Keemik pärib superklassist Tudeng.
def aita_tudengit(self): # Klassi Keemik meetod.
print("See lektor on kohutav.")
juuli = Tudeng() # Loome klassi Tudeng objekti.
mati = Keemik() # Loome klassi Keemik objekti.
mati.tereta() # Meetod tereta päriti.
mati.aita_tudengit() # Klassi Keemik meetod.
juuli.tereta() # Klassi Tudeng meetod.
juuli.aita_tudengit() # Baasklassis Tudeng meetod puudub.
Tere! See lektor on kohutav. Tere!
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Cell In[13], line 21 18 mati.aita_tudengit() # Klassi Keemik meetod. 20 juuli.tereta() # Klassi Tudeng meetod. ---> 21 juuli.aita_tudengit() AttributeError: 'Tudeng' object has no attribute 'aita_tudengit'
Päritud meetodi ülekirjutamine (overriding) ja polümorfism¶
Kui sa soovid võid peale pärimist mõne meetodi üle kirjutada kasutades sama nime. Kehtima jääb ülekirjutatud meetod:
class Tudeng: # Baas- ehk superklass.
def tereta(self): # <---- Algne meetodi definitsioon.
print("Tere!")
def aita_tudengit(self):
print("See lektor on kohutav.")
class Keemik(Tudeng): # Pärime superklassist Tudeng.
def tereta(self): # <---- Kirjutan meetodi üle.
print("Tere! Keemikud on kõige targemad.")
juuli = Tudeng() # Loome klassi Tudeng objekti.
mati = Keemik() # Loome klassi Keemik objekti.
mati.tereta() # Klassis Keemik ülekirjutatud meetod.
mati.aita_tudengit() # Meetod aita_tudengit päriti.
juuli.tereta() # Klassi Tudeng meetod.
juuli.aita_tudengit() # Klassi Tudeng meetod.
Tere! Keemikud on kõige targemad. See lektor on kohutav. Tere! See lektor on kohutav.
Eelolevas näites sõltub meetodi tereta
käitumine tudengi andmetüübist (Tudeng, Keemik) ehk objekti klassist.
Polümorfism $-$ omadus kus sama (nimega) meetodi käitumine sõltub objekti klassist ehk objekti andmetüübist.
- Polymorphism $-$ etymology: word comes from the greek roots "poly" (many) and "morphe" (form). Unlike "form" in English, "morphe" does not mean "shape." It is a philosophical term that means "the outward expression of an inner essence."
Pythoni sisseehitatud klassid ja pärilikkus¶
Eespool nägime, et konstruktor funktsioon pole klassi defineerimisel kohustuslik. Kuidas Python teab initsialiseerida objekte kui me ei kirjuta konstruktorit. See päritakse taustal vaikimisi ehk automaatselt koos muu vajalikuga baasklassist nimega object
.
Taustal pärivad kõik klassid baasklassist nimege object
. See tähendab, et klassi saab defineerida ka nii:
class Auto(object): # Pärimine superklassist object (NB! pole kohustulik).
värv = 'kollane'
def __init__(self, kiirus = 0):
self.kiirus = kiirus
def get_asukoht(self, aeg):
asukoht = self.kiirus * aeg
print('Auto asub', asukoht, 'km kaugusel.')
auto = Auto(60)
auto.get_asukoht(0.5)
Auto asub 30.0 km kaugusel.
Python võimaldab luua baasklassiga defineeritud objekti kasudades sisseehitatud funktsiooni object
. Selliselt loodud objektile pole võimalik lisada uusi meetodeid.
obj = object()
print(obj)
print(dir(obj))
<object object at 0x12fadaa50> ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
Info ja dokumentatsioon baasklassi object
kohta:
import builtins
#help(object)
Method resolution order (MRO)¶
Päritud klasside meetodite ja muutujate prioriteetsus määratakse MRO eeskirjaga. MRO eeskiri dikteerib mis järjekorras omistatakse päritud ja ülekirjutatud atribuutidele ning meetoditele väärtusi (mis klassi skoobist otsime väärtusi). MRO eeskiri on sarnane LEGB reegliga mida tutvustasime Loengus 6. Eriti kasulik kui uuritav klass on pärinud mitmest erinevast superklassist.
class A:
a = 1
b = 2
class B(A):
b = 3
obj = B()
print(obj.a, obj.b) # Muutuja b väärtus kirjutati üle.
1 3
MRO eeskirja saab igal ajal järgi vaadata:
help(B) # Info sisaldub klassi abimatejalis ja dokumentatsioonis.
Help on class B in module __main__: class B(A) | Method resolution order: | B | A | builtins.object | | Data and other attributes defined here: | | b = 3 | | ---------------------------------------------------------------------- | Data descriptors inherited from A: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) | | ---------------------------------------------------------------------- | Data and other attributes inherited from A: | | a = 1
Altenatiivselt ja lühema väljundiga:
B.mro()
[__main__.B, __main__.A, object]
B.__mro__
(__main__.B, __main__.A, object)
Spetsiaalsed või maagilised meetodid (special, magic or dunder methods):¶
Valik klassiga seotud spetsiaalseid meetodeid¶
Nimi "dunder method" tuleb väljendist "double underscore" $\rightarrow$ "double" + "under" $\rightarrow$ "dunder". Nimetus "maagiline" viitab asjaolule, et selliste meetodite kasutamine, justkui maagiliselt, paneb programmi tegema kõikvõimelikke kasulikke asju.
Valik maagilisi meetodeid:
__init__
- Konstruktor funktsioon.__str__
- Objekti esitused stringina (inimloetavam). Meetodile vastab funktsioonstr
.__repr__
- Objekti esitused stringina. Meetodile vastab funktsioonrepr
.__lt__
,__eq__
,__gt__
, jm. $-$ Võrdlusoperaatorid ja tehted mis päritakse superklassistobject
objektide võrdlemiseks (allpool laiendame teemat). Nendele meetoditele vastavad Pythoni operaatorid<
,==
ja>
, jm.__add__
,__sub__
,__mul__
, jm. $-$ Aritmeetilised tehted objektidega (allpool laiendame teemat). Nendele meetoditele vastavad Pythoni operaatorid+
,-
ja*
, jm.
class Auto:
def __init__(self, kiirus, mudel, hind): # Konstruktor on maagiline meetod.
self.kiirus = kiirus # Objekti muutuja.
self.mudel = mudel
self.hind = hind
auto = Auto(12, 'BMW', 4000) # Loon objekti.
# Need maagilised meetodid päriti klassist object.
print(auto.__str__())
print(auto.__repr__())
print(auto)
print(str(auto)) # Maagilisele meetodile vastav funktsioon str.
print(repr(auto)) # Maagilisele meetodile vastav funktsioon repr.
print(dir(auto)) # Lisaks str ja repr meetoditele sisaldab ka meie loodud objektimuutujaid.
<__main__.Auto object at 0x14facaf90> <__main__.Auto object at 0x14facaf90> <__main__.Auto object at 0x14facaf90> <__main__.Auto object at 0x14facaf90> <__main__.Auto object at 0x14facaf90> ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'hind', 'kiirus', 'mudel']
Spetsiaalse või maagilise meetodi ülekirjutamine (overriding, overloading)¶
Nii nagu tavalisi meetodeid, mida arutasime eespoole, saame ka spetsiaalseid vaikimisi kaasaantud meetodeid muuta, neid ülekirjutades (overriding) või neid ülekirjutades ja nende funktsionaalsust laiendades (overloading). Ülekirjutamine annab meile võimaluse meetodi käitumist muuta meie objektidele sobivamaks. Selleks, et päritud meetodit ülekirjutada kasuta selle nime:
class Auto:
def __init__(self, kiirus, mudel, hind): # Konstruktor on spets. meetod.
self.kiirus = kiirus # Objekti muutujad.
self.mudel = mudel
self.hind = hind
def __str__(self): # Kirjutan olemasoleva üle, kasutades selle nime.
return '{} auto hinnaga {} sõidab {} km/h.'.format(
self.mudel,
self.hind,
self.kiirus)
def __repr__(self): # Kirjutan olemasoleva üle, kasutades selle nime.
return "{}('{}', '{}', '{}')".format(
type(self).__name__,
self.mudel,
self.hind,
self.kiirus)
auto = Auto(12, 'BMW', 4000) # Loon objekti
print(auto)
print(str(auto)) # Sama mis eelmine.
print(repr(auto))
BMW auto hinnaga 4000 sõidab 12 km/h. BMW auto hinnaga 4000 sõidab 12 km/h. Auto('BMW', '4000', '12')
Võrdlus- ja aritmeetikaoperaatoritega seotud spetsiaalsed meetodid¶
Võrdlusoperaatoreid käsitlesime Loengus 3.
Võrdlus operaatorid https://docs.python.org/3/reference/datamodel.html#basic-customization:
object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)
Avaldise x < y
taustal kutsutakse välja x.__lt__(y)
või object.__lt__(x, y)
, jne.
x, y = 2, 5
x < y
True
x.__lt__(y)
True
int.__lt__(x, y) # Klass int on klassi object alamklass.
True
x, y = 12.4, 1.8
float.__lt__(x, y)
False
float.__gt__(x, y)
True
Aritmeetikaoperaatoreid käsitlesime Loengus 3.
Aritmeetika operaatorid https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types:
object.__add__(self, other)
object.__sub__(self, other)
object.__mul__(self, other)
object.__matmul__(self, other)
object.__truediv__(self, other)
object.__floordiv__(self, other)
object.__mod__(self, other)
object.__divmod__(self, other)
object.__pow__(self, other[, modulo])
object.__lshift__(self, other)
object.__rshift__(self, other)
Avaldise x + y
taustal kutsutakse välja x.__add__(y)
või object.__add__(x, y)
, jne.
x, y = 12.4, 1.8
x + y
14.200000000000001
x.__add__(y)
14.200000000000001
float.__add__(x, y) # Klass float on klassi object alamklass.
14.200000000000001
Võrdlus- ja aritmeetikaoperaatorite ülekirjutamine¶
Sarnaselt vaikimisi kaasaantud spetsiaalsetele meetoditele saame ka Pythoni operaatoritele vastavaid spetsiaalseid meetodeid ülekirjutada. Näiteks saame luua enda defineeritud objektide liitmise operaatori ehk kirjutada operaatori +
üle. Kusjuures me ei pea operaatoritega seotud klassi object
ilmutatud kujul pärima, kuna mäletatavasti teostatakse pärimine taustal automaatselt. Algse definitsiooni saad järgi vaadata Pythoni lähtekoodist (Spyder IDE: Ctrl + G), dokumentatsioonist või Internetist.
Pythoni aritmeetika- ja muid operaatorid saab enda klassis alati ülekirjutada (operator overriding, operator overloading).
Näide 1: Liitmise operaator +
ehk meetod __add__
.
class MyInt:
def __init__(self, väärtus):
self.väärtus = väärtus
def __add__(self, other): # Argument other on my_int objekt.
print('Vastus on: ', end='') # Meie panus :)
return self.väärtus + other.väärtus # Eeldan, et otheril muutuja olemas.
a1 = MyInt(1) # Täisarvu objekti loomine.
a2 = MyInt(3)
print(a1.__add__(a2))
print(MyInt.__add__(a1, a2))
print(a1 + a2) # Meetodile __add__ vastav operaator.
print(MyInt(2) + MyInt(4))
Vastus on: 4 Vastus on: 4 Vastus on: 4 Vastus on: 6
Natukene põhjalikum kood mis tagab parema operaatoti +
töö:
class MyInt:
def __init__(self, väärtus):
self.väärtus = väärtus
def __add__(self, other): # Argument other on my_int objekt.
summa = self.väärtus + other.väärtus # Eeldan, et other'il muutuja olemas.
return MyInt(summa) # Loome uue objekti. Väljastab objekti andmetüübiga MyInt.
def __str__(self): # Esitus sõnena konsoolis ja mujal.
return f'{self.väärtus}'
def __repr__(self): # Meetodile vastab funtsioon repr.
return f'MyInt arv väärtudega {self.väärtus}.'
a1 = MyInt(1) # Täisarvu objekti loomine.
a2 = MyInt(2)
a3 = MyInt(3)
print(a1.__add__(a2).__add__(a3))
print(MyInt.__add__(MyInt.__add__(a1, a2), a3))
print(a1 + a2 + a3) # Meetodile __add__ vastav operaator.
print(repr(a1))
print(a1)
a1
6 6 6 MyInt arv väärtudega 1. 1
MyInt arv väärtudega 1.
Näide 2: Võrdlusoperaator ==
ehk meetod __eq__
. Tehniliselt ja vaikimisi operaator ==
kontrollib kas objekti või muutja nimed viitavad samale objektile arvuti mälus (id(obejkt1)
= id(objekt2)
). See käitumine pole alati programmeerijale sobiv. Kirjutame operaatori üle nii, et see suudaks võrdseks lugeda objekte millede väärtused ehk sisu on samad olenemata objektide asukohast mälus:
- Vaikimisi käitumine.
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
p1 = Point2D(1, 3) # Loon kaks punkti.
p2 = Point2D(1, 3)
p3 = p2 # Sama objekt.
print(id(p1))
print(id(p2))
print(id(p3))
print(p1 == p2) # Erinevad objektid.
print(p3 == p2) # Nimed viitavad samale objektile.
5631741776 5631655568 5631655568 False True
- Ülekirjutatud käitumine.
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
def get_cordinates(self):
cords = (self.x, self.y)
print(cords)
def __eq__(self, other): # Muudame vaikimisi käitumise.
if isinstance(other, Point2D):
return self.x == other.x and self.y == other.y
return False
p1 = Point2D(1, 3) # Loon kaks punkti.
p2 = Point2D(1, 3)
p1.get_cordinates()
p2.get_cordinates()
print(id(p1))
print(id(p2))
print(p1 == p2)
(1, 3) (1, 3) 5631774352 5631742160 True
Peale meetodi __eq__
ülekirjutamist on Pythonis endiselt võimalik kontrollida kas objektid on samad objekt arvuti mälus. Kasuta operaatorit is
:
p1 is p2 # See võimalus jääb alati, kasutaja ei saa seda operaatorit muuta.
False
x = y = 2
print(x + y)
print(x.__add__(y))
print(int.__add__(x, y))
4 4 4
x, y = 'a', 'b'
print(x + y)
print(x.__add__(y))
print(str.__add__(x, y))
ab ab ab
x, y = [1, 2], [3]
print(x + y)
print(x.__add__(y))
print(list.__add__(x, y))
[1, 2, 3] [1, 2, 3] [1, 2, 3]
Funktsioon¶
Näide: Funktsioon len
on polümorfne.
print(len("Programiz")) # Sõne tähemärgid.
print(str.__len__("Programiz")) # Vastav maagiline meetod.
9 9
print(len(["Python", "Java", "C"])) # Listi liikmed.
print(list.__len__(["Python", "Java", "C"])) # Vastav maagiline meetod.
3 3
print(len({"Name": "John", "Address": "Nepal"})) # Sõnaraamatu võtmeted.
print(dict.__len__({"Name": "John", "Address": "Nepal"})) # Vastav maagiline meetod.
2 2
Muud klassiga seotud teemad¶
Kasulikud sisseehitatud funktsioonid¶
Allolevad funktsiooni täiendavad Loengus 8 arutatud oluliste sisseehitatud funktsioone nimistut.
Funktsioonid: isinstance
ja issubclass
¶
Kas objekt (class instance) on loodud mingis konkreetses klassis? Kas objekt on kindla klassi instants? Kasuta süntaksit:
isinstance(<objekt>, <klass>)
isinstance(auto, Auto)
True
isinstance(auto, A)
False
Kas klass on teise klassi alamklass? Kasuta süntaksit:
issubclass(<alamklass>, <superklass>)
issubclass(B, A)
True
issubclass(A, B)
False
Funktsioonid: hasattr
, getattr
, setattr
, delattr
ja vars
¶
Funktsioonid on seotud klassi- ja objektimuutujatega ehk atribuutidega.
Kasutamise süntaks (tutvu iseseisvalt):
hasattr(<object>, <name>)
getattr(<object>, <name>[, <default>])
setattr(<object>, <name>, <value>)
delattr(<object>, <name>)
vars([<object>])
Näide: Funktsioon setattr
kasutus.
class Auto:
color = ''
def __init__(self, kiirus = 0):
self.kiirus = kiirus
auto = Auto()
setattr(Auto, 'color', 'green')
print(Auto.color)
setattr(auto, 'kiirus', 100) # Kui muutujat kiirust poleks siis luuaks.
print(auto.kiirus)
setattr(auto, 'vanus', 30) # Uus objektiga seotud muutuja. Luuakse.
print(auto.vanus)
green 100 30
Näide: Funktsioon getattr
kasutus.
color = getattr(Auto, 'color')
auto_kiirus = getattr(auto, 'kiirus')
auto_vasus = getattr(auto, 'vanus')
print(color)
print(auto_kiirus)
print(auto_vasus)
green 100 30
class Tudeng: # Baas- ehk superklass.
"""A one-line docstring."""
def tereta(self):
"""A one-line docstring."""
print("Tere!")
class Tudeng: # Baas- ehk superklass.
"""A multi-line
docstring.
"""
def tereta(self):
"""A multi-line
docstring.
"""
print("Tere!")
Klasside importimisest¶
Siin kehtivad samad reeglid mida arutasime Loengutes 7 ja 8.
Kindla klassi importimine moodulist:
from <moodul> import <klass>
Kogu mooduli, mis sisaldab erinevaid klasse, importimine:
import <moodul>