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.¶

Viimati uuendatud 19.11.2024.

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:

In [1]:
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?

  1. Nagu just mainitud on Python ülesse ehitatud objektorjeteerituse baasil. Seega selleks, et teada kuidas keelt paremini ja oskuslikumalt kasutada võiksime OOPi teada.
  2. 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.

Tabel 1. Tudengite andmed esitatuna tabeli kujul. Üldjuhul on tudengite arv n suur.
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.

In [2]:
# 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:

In [3]:
# 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.

In [4]:
# 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:

In [5]:
# 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¶

No description has been provided for this image
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
  • 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:

In [6]:
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.

In [7]:
# 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.

In [8]:
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
No description has been provided for this image
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.

In [9]:
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.
No description has been provided for this image
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.

In [10]:
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:

In [11]:
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:

In [12]:
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:

In [13]:
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:

In [14]:
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:

In [15]:
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.

In [16]:
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:

In [17]:
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.

In [18]:
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:

In [19]:
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:

In [20]:
B.mro()
Out[20]:
[__main__.B, __main__.A, object]
In [21]:
B.__mro__
Out[21]:
(__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 funktsioon str.
  • __repr__ - Objekti esitused stringina. Meetodile vastab funktsioon repr.
  • __lt__, __eq__, __gt__, jm. $-$ Võrdlusoperaatorid ja tehted mis päritakse superklassist object 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.
In [22]:
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:

In [23]:
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.

In [24]:
x, y = 2, 5
x < y
Out[24]:
True
In [25]:
x.__lt__(y)
Out[25]:
True
In [26]:
int.__lt__(x, y)  # Klass int on klassi object alamklass.
Out[26]:
True
In [27]:
x, y = 12.4, 1.8
float.__lt__(x, y)
Out[27]:
False
In [28]:
float.__gt__(x, y)
Out[28]:
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.

In [29]:
x, y = 12.4, 1.8
x + y
Out[29]:
14.200000000000001
In [30]:
x.__add__(y)
Out[30]:
14.200000000000001
In [31]:
float.__add__(x, y)  # Klass float on klassi object alamklass.
Out[31]:
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__.

In [32]:
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öö:

In [33]:
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
Out[33]:
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:

  1. Vaikimisi käitumine.
In [34]:
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
  1. Ülekirjutatud käitumine.
In [35]:
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:

In [36]:
p1 is p2  # See võimalus jääb alati, kasutaja ei saa seda operaatorit muuta.
Out[36]:
False

Pythoni sisseehitatud operaatorite ja funktsioonide polümorfism¶

NB! Tehniliselt polümorfismi definitsioon eeldab, et kuskil või kuidagi toimub nii pärimine kui ka meetodi ülekirjutamine.

Operaator¶

Näide: Liitmise operaator + on polümorfne.

In [37]:
x = y = 2
print(x + y)
print(x.__add__(y))
print(int.__add__(x, y))
4
4
4
In [38]:
x, y = 'a', 'b'
print(x + y)
print(x.__add__(y))
print(str.__add__(x, y))
ab
ab
ab
In [39]:
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.

In [40]:
print(len("Programiz"))  # Sõne tähemärgid.
print(str.__len__("Programiz"))  # Vastav maagiline meetod.
9
9
In [41]:
print(len(["Python", "Java", "C"]))  # Listi liikmed.
print(list.__len__(["Python", "Java", "C"]))  # Vastav maagiline meetod.
3
3
In [42]:
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>)
In [43]:
isinstance(auto, Auto)
Out[43]:
True
In [44]:
isinstance(auto, A)
Out[44]:
False

Kas klass on teise klassi alamklass? Kasuta süntaksit:

issubclass(<alamklass>, <superklass>)
In [45]:
issubclass(B, A)
Out[45]:
True
In [46]:
issubclass(A, B)
Out[46]:
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.

In [47]:
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.

In [48]:
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

Klasside ja meetodite dokumenteerimine¶

Koodi süntaksistiili ja dokumenteerimise soovituste kohat loe lisaks: PEP 257. Klasse ja nende meetodeid dokumeteeritakse sarnaselt Pythoni funktsioonidele:

In [49]:
class Tudeng:  # Baas- ehk superklass.
    """A one-line docstring."""
    
    def tereta(self):
        """A one-line docstring."""
        print("Tere!")
In [50]:
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>

Lisa: Näiteid iseseisvalt uurimiseks¶

Siit leiad hulga kommenteeritud klasside loomisega ja nende objektidega opereerimise näiteid. Viidatud näidisklassid defineerivad TTÜ tudengite objekte ehk tudengeid.

















☻   ☻   ☻