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

Koodinäited¶

1  Objektorjenteeritud programmeerimine (OOP)¶

1.1  Miks on OOP kasulik?¶

Osa teist on enda teadmata juba programmeerinud kasutades OOPi. Pythonis on kõik andmetüübi esindajad OOP objektid. Pythoni andmetüübid on defineertud 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 probleeme lahendades kasutanud Pythonit 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.

1.1.1  Näide suhteliselt lihtsast probeelmist mida saaks lahendada kasutades OOPi¶

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.

Esitame probleemi püstituse ja selle kaks lahendust kasutades meile teadaolevaid programmeerimise võtteid.

1.1.2  Probleem ja 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 telefoninumbrid 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]]

1.1.3  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 telefoninumbrid 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 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).

1.2  OOP ja Python¶

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.

1.3  OOP programmeerimise paradigma¶

OOPis opereerime objektidega ja klassidega lisaks muutujatele, avaldistele, erinevatele koodi 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 plokkis nimega klass (defineerime lausendiga class).

Klass (class) -- objekti loomise eeskiri millega käivad kaasas klassi ja selle objekte kirjeldavad atribuudid (andmed, muutujad) ja objektiga seotud tegevused (meetodid, spetsiaalsed/maagilised meetodid, jne.). Eeskiri milla alusel luuakse objekte.

1.3.1  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

1.3.2  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


2  Klassi defineerimine ja objekti loomine¶

2.1  Klass kus puuduvad meetodid¶

Klassi defineerimiseks kasutame lausendit class. Klassi sisu eristame muust koodist taandega nii nagu ikka Pythoni plokkides. 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 selle 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]:
obj = A()  # Objekti loomine, class instance. 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 0x104d9f950>
<class '__main__.A'>

Real 1 oleva objekti defineerimiseks vajalik kood on programmeerija eest varjatud ja teostub automaatselt või maagiliselt. Python teostas automaatselt kõik vajaliku objekti loomiseks.

2.2  Klass koos konstruktoriga¶

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 klassiploki sees spetsiaalset/maagilist meetodit ehk funktsiooni nimega __init__. Spetsiaalset meetodit __init__ nimetatakse ka maagiliseks meetodiks. Spetsiaalne meetod __init__ omab kohustuslikku positsionaalset argumenti, millele pannakse tavaliselt nimeks self. Argumendiga self tähistame objekti mida saame luua kasutades hetkel kirjutatavat klassi, vt. Joonis 2 ja järgmist lõiku.

2.2.1  Näide: klass Auto¶

Auto ja selle mudel. See klass nimega Auto loob alati BMW-sid:

In [8]:
class Auto:
    
    def __init__(self):  # Konstruktor.
        self.mudel = 'BMW'  # Objekti muutuja/atribuut, instance variable.
    

auto = Auto()  # Loon objekti "auto" kasutades klassi Auto.

print(auto.mudel)
BMW

Tavaliselt loome objekte dünaamilisemalt lisades konstruktorile argumente:

In [9]:
class Auto:
    
    def __init__(self, mudel):  # Konstruktor, toetab argumentide kasutust.
        self.mudel = mudel      # Objekti muutuja/atribuut, instance variable.
    

auto = Auto('BMW')  # Loon objekti "auto" kasutades klassi Auto.

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.

2.3  Klass koos enda kirjutatud meetoditega¶

Selleks, et meetodid siduda kindla objekti initsialiseeringuga kasutame ka nendes spetsiaalset argumenti või muutujat self (esimene positsionaalne argument). Joonis 3 näitab kuidas mõelda spetsiaalsest muutujast self tavaliste meetodite kontekstis.

2.3.1  Näide: klass Auto¶

Lisame klassile Auto kaks enda kirjutatud meetodit millega saame kontrollida auto kiirust ja asukohta:

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

Märkus: Muutuja ja argument self seob def-plokkide lokaalseid skoope. Mäletatavasti on Pythonis def-plokk ainuke lokaliseeriv skoop. Kui meetodi esimene argument on self siis sellel meetodil on juurdepääs konstruktori def-plokki muutujatele ehk objekti dünaamilistele muutujatele, vrd. Joonis 3.

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 [11]:
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 [12]:
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 objekti auto kasutades klassi Auto.

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.

3  Pärilikkus (inheritance), baas- või superklass ja alamklass¶

Pärimine on võte mis võimaldab kirjutatud koodi taaskasutamist.

3.1  Staatiliste klassimuutujate 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 olemasolevate klasside definitsioonide 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 [13]:
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']

3.2  Meetodite pärimine enda loodud superklassist¶

Sarnaselt statilistele muutujatele saame pärida meetodeid:

In [14]:
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[14], line 20
     17 mati.aita_tudengit()  # Klassi Keemik meetod.
     19 juuli.tereta()  # Klassi Tudeng meetod.
---> 20 juuli.aita_tudengit()

AttributeError: 'Tudeng' object has no attribute 'aita_tudengit'

3.3  Päritud meetodi ülekirjutamine (overriding) ja polümorfism¶

Kui sa soovid võid peale pärimist mõne meetodi üle kirjutada kasutades selle algset nime. Kehtima jääb ülekirjutatud meetod:

In [15]:
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 (vt. ka Lõik 5) $-$ 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."

3.4  Pythoni standardteegi klassid ja pärilikkus¶

Eespool nägime, et konstruktor __init__ pole klassi defineerimisel kohustuslik. Kuidas Python teab initsialiseerida objekte kui me ei kirjuta konstruktorit? Vastus: See meetod päritakse taustal vaikimisi ehk automaatselt koos muu vajaliku koodiga. Standardteegis eksisteerib baasklass nimega object (jah see on väikese tähega) mida kasutatakse selleks otstarbeks.

Taustal pärivad kõik klassid baasklassist nimege object. See tähendab, et klassi saab defineerida ka nii:

In [16]:
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 atribuute ega meetodeid.

In [17]:
obj = object()

print(obj)
print(dir(obj))
<object object at 0x1123daa50>
['__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 [18]:
import builtins
#help(object)

Pärime klassi list ja muudame selle meetodi append käitumist lisades sellele logi kirjutamise funktsionaalsuse:

In [19]:
class LoggedList(list):
    
    def append(self, item):  # Kirjutan üle.
        print(f"Appending {item}")
        list.append(self, item)
        #super().append(item)  # Nii saab ka, funktsioonist super räägime järgmine nädal.


ll = LoggedList()  # Tühja listi loomine.
ll.append(10)
ll.append(20)
print(ll)
Appending 10
Appending 20
[10, 20]

3.5  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. MRO info on eriti kasulik kui uuritav klass pärib mitmest erinevast superklassist.

In [20]:
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, nt. lugedes klassi vaikimisi genereeritud dokumentatsiooni:

In [21]:
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 infot tagastab meetod mro:

In [22]:
B.mro()
Out[22]:
[__main__.B, __main__.A, object]

või kasutades muutujat __mro__:

In [23]:
B.__mro__
Out[23]:
(__main__.B, __main__.A, object)

4  Spetsiaalsed ehk maagilised meetodid (special, magic or dunder methods)¶

Nimetus "maagiline" viitab asjaolule, et sellised meetodid teostavad automaatselt teatud kasulikke tegevusi. Tegevused teostatakse lihtsalt sellepärast, et programeerija kasutab nende nimesid.

4.1  Valik spetsiaalseid ehk maagilisi meetodeid¶

Maagilised meetodid on tavaliselt kirjutataud kasutades selle nime ees ja taga kahte alakriipsu __nimi__. Nimi "dunder method" tuleb väljendist "double underscore" $\rightarrow$ "double" + "under" $\rightarrow$ "dunder".

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.

4.1.1  Meetodite __str__ ja __repr__ kasutamine¶

Märkus: Järgmise nädala loengus räägime täpsemalt mis erinevus on meetoditel __str__ ja __repr__ ja kuidas neid tavaliselt kasutatakse.

In [24]:
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 eksisteerivad siin meie loodud objektimuutujaid hind, kiirus ja mudel.
<__main__.Auto object at 0x136fdd850>
<__main__.Auto object at 0x136fdd850>
<__main__.Auto object at 0x136fdd850>
<__main__.Auto object at 0x136fdd850>
<__main__.Auto object at 0x136fdd850>
['__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']

4.1.2  Spetsiaalse ehk maagilise meetodi ülekirjutamine (overriding, overloading)¶

Nii nagu tavalisi enda kirjutatud meetodeid, mida arutasime eespoole, saame ka spetsiaalseid vaikimisi kaasaantud meetodeid muuta:

  • Neid ülekirjutades (overriding) $-$ meetodi sisu täielik muutus.
  • Neid osaliselt ülekirjutades (overloading) $-$ meetodi funktsionaalsuse laiendamine.

Ülekirjutamine annab meile võimaluse meetodi käitumist muuta endaloodud objektidele sobivamaks. Selleks, et päritud meetodit üle kirjutada kasuta selle nime:

In [25]:
class Auto:
    
    def __init__(self, kiirus, mudel, hind):  # Konstruktor on maagiline 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')

4.1.3  Võrdlus- ja aritmeetikaoperaatoritega seotud spetsiaalsed meetodid¶

Võrdlus- ja aritmeetikaoperaatorid on salvestatud baasklassis object, me nägime seda juba eespool Lõigus 3.4:

In [26]:
obj = object()
print(dir(obj))
['__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__']

4.1.3.1  Võrdlusoperaatorid¶

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

4.1.3.2  Aritmeetikaoperaatorid¶

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

4.1.4  Võrdlus- ja aritmeetikaoperaatorite ülekirjutamine¶

Sarnaselt vaikimisi kaasaantud spetsiaalsetele meetoditele saame ka Pythoni operaatoritele vastavaid spetsiaalseid meetodeid üle kirjutada. 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 kirjutatavas klassis alati üle kirjutada (operator overriding, operator overloading).

4.1.4.1  Liitmise operaatori ülekirjutamine¶

Liitmise operaator + ehk meetod __add__. Lisame liitmisele kommentaari:

In [35]:
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 [36]:
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.
        #return type(self)(summa)  # Nii saab ka.

    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[36]:
MyInt arv väärtudega 1.

4.1.4.2  Võrdlusoperaatori ülekirjutamine¶

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)). Selline käitumine pole igas olukorras sobiv. Kirjutame operaatori üle nii, et see suudaks võrdseks lugeda objekte millede väärtused ehk sisu on samad olenemata objektide asukohast mälus (vt. lisaks Loeng 7 Lõik 3.2):

  1. Vaikimisi käitumine.
In [37]:
class Point2D:

    def __init__(self, x, y):
        self.x = x
        self.y = y


p1 = Point2D(1, 3)  # Loon kaks punkti.
p2 = Point2D(1, 3)  # Uus objekt sama sisu.
p3 = p2  # Samad objektid.

print(p1)
print(p2)
print(p3)

print(id(p1))
print(id(p2))
print(id(p3))

print(p1 == p2)  # Erinevad objektid.
print(p3 == p2)  # Nimed viitavad samale objektile.
<__main__.Point2D object at 0x136fde8d0>
<__main__.Point2D object at 0x136fdf690>
<__main__.Point2D object at 0x136fdf690>
5217577168
5217580688
5217580688
False
True
  1. Ülekirjutatud käitumine. Kirjutama üle meetodid __eq__ ja ka __repr__:
In [38]:
class Point2D:

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return '({}, {})'.format(self.x, self.y)
        
    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)  # Uus objekt sama sisu.
p3 = p2  # Samad objektid.

print(p1)
print(p2)
print(p3)

print(id(p1))
print(id(p2))
print(id(p3))

print(p1 == p2)  # Erinevad objektid.
print(p3 == p2)  # Nimed viitavad samale objektile.
(1, 3)
(1, 3)
(1, 3)
5217667664
5217571344
5217571344
True
True

Peale meetodi __eq__ ülekirjutamist on Pythonis endiselt võimalik kontrollida kas objektid on samad objekt arvuti mälus. Kasuta operaatorit is:

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

5  Pythoni sisseehitatud operaatorite ja funktsioonide polümorfism¶

NB! Tehniliselt polümorfismi definitsioon eeldab, et kuskil või kuidagi toimub nii pärimine (nt. klassist object) kui ka meetodi ülekirjutamine mis toimub alamklassis.

5.1  Polümorfne operaator¶

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

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

5.2  Polümorfne funktsioon¶

Näide: Funktsioon len on polümorfne.

In [43]:
print(len("Programiz"))  # Sõne tähemärgid.
print(str.__len__("Programiz"))  # Vastav maagiline meetod.
9
9
In [44]:
print(len(["Python", "Java", "C"]))  # Listi liikmed.
print(list.__len__(["Python", "Java", "C"]))  # Vastav maagiline meetod.
3
3
In [45]:
print(len({"Name": "John", "Address": "Nepal"}))  # Sõnaraamatu võtmeted.
print(dict.__len__({"Name": "John", "Address": "Nepal"}))  # Vastav maagiline meetod.
2
2

6  Muud klassiga seotud teemad¶

6.1  Kasulikud sisseehitatud funktsioonid¶

Allolevad funktsiooni täiendavad Loengus 8 arutatud oluliste sisseehitatud funktsioone nimistut.

6.1.1  Funktsioonid: isinstance ja issubclass¶

Funktsioon isinstance kontrollib kas objekt (class instance) on loodud mingis konkreetses klassis? Kas objekt on kindla klassi instants? Kasuta süntaksit:

isinstance(<objekt>, <klass>)
In [46]:
isinstance(auto, Auto)
Out[46]:
True
In [47]:
isinstance(auto, A)
Out[47]:
False

Funktsioon issubclass kontrollib kas klass on teise klassi alamklass? Kasuta süntaksit:

issubclass(<alamklass>, <superklass>)
In [48]:
issubclass(B, A)
Out[48]:
True
In [49]:
issubclass(A, B)
Out[49]:
False

6.1.2  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 [50]:
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 [51]:
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

6.2  Klasside ja meetodite dokumenteerimine¶

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

In [52]:
class Tudeng:
    """A one-line docstring."""
    
    def tereta(self):
        """A one-line docstring."""
        print("Tere!")
In [53]:
class Tudeng:
    """A multi-line
    docstring.
    """
    
    def tereta(self):
        """A multi-line
        docstring.
        """
        
        print("Tere!")

6.3  Klasside importimisest¶

Siin kehtivad samad reeglid mida arutasime Loengutes 7 ja 8.

Kindla klassi importimine moodulist:

from <moodul> import <klass>

Kogu mooduli, mis sisaldab mitmeid klasse, importimine:

import <moodul>

6.4  Funktsioon callable, meetod __call__ ja selle kasutus¶

Loengus 8 Lõigus 1.3.13 tutvustasime funktsiooni callable mis kontrollis kas objekti klassis kasutatake meetodit __call__. See meetod võimaldab objektil olla väljakutsutav funktsioonilaadne objekt.

Funktsiooni callable dokumentatsioon:

In [54]:
callable?
Signature: callable(obj, /)
Docstring:
Return whether the object is callable (i.e., some kind of function).

Note that classes are callable, as are instances of classes with a
__call__() method.
Type:      builtin_function_or_method

Kuidas meetodit kasutada. Objekt muutub funktsioonilaadseks kui sellel on defineeritud meetod __call__. Kui meetod on defineeritud siis saame objekti a välja kutsuda kahel erineval viisil:

  • a.__call__(arg1, arg2, ...) või lihtsamalt,
  • a(arg1, arg2, ...).

6.4.1  Meetodi __call__ kasutus¶

Lihtne näide, programm mis väljastab sõne "Hello, Juulius!":

In [55]:
class Greeter:
    
    def __call__(self):
        print("Hello, Juulius!")


gf = Greeter()  # Objekti loomine.

gf.__call__()
gf()  # Objekt on väljakutsutav nagu funktsioon.
Hello, Juulius!
Hello, Juulius!

Sama programm koos argumentidega:

In [56]:
class Greeter:
    
    def __call__(self, name):
        return f"Hello, {name}!"


gf = Greeter()  # Objekti loomine.

print(gf.__call__("Juulius"))
print(gf("Juulius"))  # Objekt on väljakutsutav nagu funktsioon.
Hello, Juulius!
Hello, Juulius!

6.4.2  Meetod mis liidab, korrutab arvulisi väärtusi¶

Liitmise näide:

In [57]:
class Adder:
    
    def __init__(self):
        self.total = 0

    def __call__(self, x):
        self.total += x
        return self.total


af = Adder()

print(af(5))  # 0 + 5 = 5
print(af(3))  # 5 + 3 = 8
print(af(2))  # 8 + 2 = 10
5
8
10

Korrutamise näide:

In [58]:
class Multiplier:
    
    def __init__(self, factor):
        self.factor = factor

    def __call__(self, value):
        return value * self.factor


double = Multiplier(2)
triple = Multiplier(3)

print(double(5))  # 2 * 5 = 10
print(triple(5))  # 3 * 5 = 15
10
15

6.4.3  Lihtne funktsioonikääre¶

Lihtne funktsioonikääre ehk funktsiooni ümbris:

In [59]:
class FuncWrapper:
    
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("Calling function...")
        return self.func(*args, **kwargs)


def square(x):
    return x * x

wrapped_square = FuncWrapper(square)  # Objekti loomine.

print(wrapped_square(4))
Calling function...
16

6.5  Meetodi dekoreerimine enda kirjutatud dekoraatoriga¶

Sarnaselt sellele kuidas me dekoreerisime funktsioone saame dekoreerida klassi meetodeid. Dekoreerimiseks saame kasutada operaatorit @ ja süntaksit kujul <meetod> = <dekoraator>(<meetod>) Dekoraator mis lisab kommentaare ja klass ilme meetodita __init__:

In [60]:
def my_decorator(func):
    def wrapper(self, *args):
        print(f"Calling method {func.__name__}")
        result = func(self, *args)
        print(f"Method {func.__name__} finished")
        return result
    return wrapper


class MyClass:
    
    @my_decorator
    def say_hello(self, name):
        print(f"Hello, {name}!")


obj = MyClass()
obj.say_hello("World!")
Calling method say_hello
Hello, World!!
Method say_hello finished

Klass milles on kasutatud konstruktorit __init__ ja kus dekoreeritakse kasutades operaatorit @:

In [61]:
class MyClass:

    def __init__(self, name):
        self.name = name
    
    @my_decorator
    def say_hello(self):
        print(f"Hello, {self.name}!")


obj = MyClass('Juulius')
obj.say_hello()
Calling method say_hello
Hello, Juulius!
Method say_hello finished

Sama näide kus kasutame dekoreerimiseks süntaksit <meetod> = <dekoraator>(<meetod>):

In [62]:
class MyClass:

    def __init__(self, name):
        self.name = name
    
    def say_hello(self):
        print(f"Hello, {self.name}!")

    say_hello = my_decorator(say_hello)  # Dekoreerimine.


obj = MyClass('Juulius')
obj.say_hello()
Calling method say_hello
Hello, Juulius!
Method say_hello finished

Lisad¶

Lisa 1: Näiteid iseseisvaks uurimiseks.
Siit leiad hulga kommenteeritud klasside loomisega ja nende objektidega opereerimise näiteid. Viidatud näidisklassid defineerivad TTÜ tudengite objekte ehk tudengeid.

















☻   ☻   ☻