Loeng 13: Objektorienteeritud programmeerimine (OOP), meetodi dekoraator, dünaamiline klassimuutuja või atribuut, klassimeetod, staatiline meetod, pärilikkus, pesastatud klass, muutuja: cls, funktsioon: super¶

Viimati uuendatud 25.11.2024.

Koodinäited¶

Eelmise nädala loengu meeldetuletuseks¶

Näide: Auto ja selle asukoht. Maagilise meetodu __init__ esimene positionaalne argument self tähistame loodavat objekti ning selle kaudu defineeritakse objekti muutujaid ja muid seotud andmeid. Igal loodud objektiga käivad kaasas selle enda andmed ja meetodid.

In [1]:
class Auto:
    
    def __init__(self, kiirus = 0): # Konstruktor. Maagiline meetod mis konstrueerib objekti.
        self.kiirus = kiirus        # Objektimuutuja ehk atribuut.
    
    def set_kiirus(self, kiirus):   # Meetod 1. Setter funktsioon.
        self.kiirus = kiirus        # Kirjutan olemasoleva väärtuse üle.
    
    def get_asukoht(self, aeg):     # Meetod 2. Getter funktsioon.
        return self.kiirus * aeg

    def __repr__(self):             # Maagiline meetod, esitus sõnena.
        return 'Auto kiirusega {}.'.format(self.kiirus)


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

print(auto)
auto.set_kiirus(60)           # [km/h]
print(auto.get_asukoht(0.5))  # [h]
print(auto)
auto
Auto kiirusega 0.
30.0
Auto kiirusega 60.
Out[1]:
Auto kiirusega 60.

Mis vahet on maagilistel meetoditel __repr__ ja __str__, millal millist kasutada? Eelmine nädal mainisime, et mõlemad on objekti esitamiseks sõne kujul konsoolis või mujal. Mis neil vahet on:

  • Meetod __repr__:
    • Kasutame koodi aremdamise ajal.
    • Esitab objekti andmeid detailsemalt.
    • Vaikimisi juht kõigi sõne esituste jaoks.
  • Meetod __str__:
    • Kasutame objekti esitamiseks lõppkasutaja jaoks.
    • Vähem tehnilisi andmeid, vähem detailsem.
    • Kui meetod __str__ pole defineeritud/üle kirjutatud kasutatake vaikimisi meetodi __repr__ väljundit.

Klassi abil saime luua mitu objekti. Loon teise auto objekti kasutades klassi Auto:

In [2]:
teine_auto = Auto()

teine_auto.set_kiirus(100)   # [km/h]
teine_auto.get_asukoht(2.0)  # [h]
Out[2]:
200.0

Klassi meetodite dekoraatorid¶

Klassides kasutamiseks on loodud dekoraatorid millega same dekoreerida klassi meetodeid. Dokoraator muudab meetodi käitumist, vrd. Loeng 8.

Valik sisseehitatud meetodite dekoraatoreid:

  • @property - Dünaamiline klassimuutuja või dünaamiline atribuut: Meetod käitub nagu klassimuutuja, väljakutse kujul <objekt>.<meetodi nimi> (sulud puuduvad).
  • @classmethod - Klassimeetod: Meetod mis ei sõltu objektist (self). Saame endiselt meetodi välja kutsuda kujul <Klassi nimi>.<meetodi nimi>() ja jääb ka võimalus teha väljakutset läbi objekti, kujul <objekt>.<meetodi nimi>(). Annab juurdepääsu mõjutada klassimuutujaid mis teatavati kehtivad kogu klassile (kõik objektid, kõik meetodid). Meetodi defineerimisel peame kasutama argumeti cls esimese positsionaalse argumendina.
  • @staticmethod - Staatiline meetod: Sõltumatu nii klassist (cls) kui ka tema objektidest (self).

Märkus 1: Pythonis esineb ka muid klassis kasutamiseks mõeldud dekoraatoreid. Siin kursuse vaatleme kõigest kolem eelmainitud.

Märkus 2: Selles kursuses käsitleme dekoraatorit @property ja selle poolt loodud atribuute lihtsustatud kujul. Tudengid kes on huvitatud täielikumast ülevaatest, lugege järgmist: https://docs.python.org/3/library/functions.html#property

Meetodi dekoraator @property¶

Meetodi dekoraatori @property kasutamise näide:

In [3]:
class Auto:
    
    def __init__(self, mudel, hind):
        self.mudel = mudel  # Objekti muutuja.
        self.hind = hind    # Objekti muutuja.

    @property
    def mudel_ja_hind(self):
        return '{}, {}'.format(self.mudel, self.hind)


auto = Auto('BMW', 4000)   # Loon objekti auto.

print(auto.mudel_ja_hind)  # Meetod mis käitub nagu klassimuutuja.
BMW, 4000

Kuna tegemist pole enam tavalise meetodiga siis väljakutse kujul auto.mudel_ja_hind() ja Auto.mudel_ja_hind(auto) pole enam võimalik:

In [4]:
auto.mudel_ja_hind()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[4], line 1
----> 1 auto.mudel_ja_hind()

TypeError: 'str' object is not callable
In [5]:
print(Auto.mudel_ja_hind(auto))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[5], line 1
----> 1 print(Auto.mudel_ja_hind(auto))

TypeError: 'property' object is not callable

Meetodi dekoraator @classmethod¶

Meetodi dekoraatori @classmethod kasutamise näide:

In [6]:
class Auto:  
    allahindlus = 0.95  # Klassimuutuja.
    
    def __init__(self, mudel, hind):
        self.mudel = mudel  # Objekti muutuja.
        self.hind = hind    # Objekti muutuja.
    
    def alanda_hinda(self):
        self.hind = Auto.allahindlus * self.hind
    
    @classmethod
    def allahindlus_maar(cls, protsent):  # NB! cls.
        cls.allahindlus = protsent  # Staatiline klassimuutuja.
        

auto = Auto('BMW', 4000)

print(Auto.allahindlus)  # Klassimuutuja järelevaatamine, otse.
print(auto.allahindlus)  # Klassimuutuja, läbi loodud objekti.

Auto.allahindlus_maar(0.90)  # Uus allahindlusmäär, kehtib kogu klassile.
print(Auto.allahindlus)  # Klassimuutuja järelevaatamine, otse.
print(auto.allahindlus)  # Klassimuutuja, läbi loodud objekti.
auto.alanda_hinda()      # Hinna alandamine.
print(auto.hind)         # Uus hind.

teine_auto = Auto('Audi', 2000)
print(teine_auto.allahindlus)  # Klassimuutuja, läbi uue objekti.
0.95
0.95
0.9
0.9
3600.0
0.9

Meetodi dekoraator @staticmethod¶

Meetodi dekoraatori @staticmethod kasutamise näide:

In [7]:
class Auto:
    
    def __init__(self, mudel, hind):
        self.mudel = mudel
        self.hind = hind   
    
    @staticmethod  
    def on_lahe_auto(mudelnimi):  # Ei sõltu argumentidest cls ega self.
        if mudelnimi == 'BMW':
            return True
        else:
            return False 


auto = Auto('BMW', 4000)

print(Auto.on_lahe_auto('Ford'))  # Väljakutse läbi klassi.
print(Auto.on_lahe_auto('BMW'))   # Väljakutse läbi klassi.

print(auto.on_lahe_auto('Ford'))  # Väljakutse läbi objekti.
print(auto.on_lahe_auto('BMW'))   # Väljakutse läbi objekti.
False
True
False
True

Pärilikkus¶

Mitmest superklassist pärimine¶

Pythoni alamklass on võimeline pärime mitmest superklassist. Superklasside arv on meelevaldne. Näide kasutades kahte superklassi:

In [8]:
class A:
    cls_A = 1

    def cls_A_method(self):
        print('Class A method.')


class B:
    cls_B = 2

    def cls_B_method(self):
        print('Class B method.')


class C(A, B):  # Pärimiene klassist A ja siis klassis B.
    cls_C = 3

    def cls_C_method(self):
        print('Class C method.')


# Väljakutsed kasutades klassi C.
print(C.cls_A)  # Päritud.
print(C.cls_B)  # Päritud.
print(C.cls_C)

cls_C_obj = C()  # Objekti loomine.

C.cls_A_method(cls_C_obj)  # Päritud.
C.cls_B_method(cls_C_obj)  # Päritud.
C.cls_C_method(cls_C_obj)

# Väljakutsed kasutades klassi C objekti.
print(cls_C_obj.cls_A)  # Päritud.
print(cls_C_obj.cls_B)  # Päritud.
print(cls_C_obj.cls_C)

cls_C_obj.cls_A_method()  # Päritud.
cls_C_obj.cls_B_method()  # Päritud.
cls_C_obj.cls_C_method()
1
2
3
Class A method.
Class B method.
Class C method.
1
2
3
Class A method.
Class B method.
Class C method.

Päritud atribuute saab loomulikult üle kirjutada:

In [9]:
class A:
    cls_A = 1

    def cls_A_method(self):
        print('Class A method.')


class B:
    cls_B = 2

    def cls_B_method(self):
        print('Class B method.')


class C(A, B):  # Pärimiene klassist A ja B.
    cls_A = 111  # Kasutan nime muudan sisu.

    def cls_B_method(self):  # Kasutan nime muudan sisu.
        print('Class B üle kirjutatud method.')


# Väljakutsed kasutades klassi C.
print(C.cls_A)  # Päritud ja üle kirjutatud.
print(C.cls_B)  # Päritud.

cls_C_obj = C()  # Objekti loomine.

C.cls_A_method(cls_C_obj)  # Päritud.
C.cls_B_method(cls_C_obj)  # Päritud ja üle kirjutatud.

# Väljakutsed kasutades klassi C objekti.
print(cls_C_obj.cls_A)  # Päritud ja üle kirjutatud.
print(cls_C_obj.cls_B)  # Päritud.

cls_C_obj.cls_A_method()  # Päritud.
cls_C_obj.cls_B_method()  # Päritud ja üle kirjutatud.
111
2
Class A method.
Class B üle kirjutatud method.
111
2
Class A method.
Class B üle kirjutatud method.

Mida ütleb method resolution order (MRO)?

In [10]:
C.mro()
Out[10]:
[__main__.C, __main__.A, __main__.B, object]
In [11]:
C.__mro__
Out[11]:
(__main__.C, __main__.A, __main__.B, object)

Seega sulgudesse paigutatud superklasside järjekord on oluline.

Mitmest superklassist pärimine ja transitiivsus¶

Päritavad superklassid saavad omakorda pärida kolmandatest superklassidest jne. Allolevas näites olevate superklasside sisu (klassimuutujad, objektimuuutjad ja meetodid) pärandatakse alamklassile:

In [12]:
class A:
    cls_A = 1

    def cls_A_method(self):
        print('Class A method.')


class B(A):  # Pärib klassi A sisu.
    cls_B = 2

    def cls_B_method(self):
        print('Class B method.')


class C(B):  # Pärib klassi B sisu, mis omakorda päris ka klassi A sisu.
    cls_C = 3

    def cls_C_method(self):
        print('Class C method.')


# Väljakutsed kasutades klassi C.
print(C.cls_A)  # Päritud läbi klassi B.
print(C.cls_B)  # Päritud klassist B.
print(C.cls_C)

cls_C_obj = C()  # Objekti loomine.

C.cls_A_method(cls_C_obj)  # Päritud läbi klassi B.
C.cls_B_method(cls_C_obj)  # Päritud klassist B.
C.cls_C_method(cls_C_obj)

# Väljakutsed kasutades klassi C objekti.
print(cls_C_obj.cls_A)  # Päritud läbi klassi B.
print(cls_C_obj.cls_B)  # Päritud klassist B.
print(cls_C_obj.cls_C)

cls_C_obj.cls_A_method()  # Päritud läbi klassi B.
cls_C_obj.cls_B_method()  # Päritud klassist B.
cls_C_obj.cls_C_method()
1
2
3
Class A method.
Class B method.
Class C method.
1
2
3
Class A method.
Class B method.
Class C method.

Pärimise omadust kus alamklass D pärib mitmest superklassist (A, B, C) järgmiselt: A $\to$ B(A) $\to$ C(B) $\to$ D(C), nimetatakse transitiivsuseks. Eelmises lauses olevas näites, oleks pärimiste järjestikune jada saanud olla meelevaldselt pikk.

Konstruktori pärimine, funktsioon super ja alamklassi objekti laiendamine¶

Klassid saavad pärida teiste klasside kõiki atribuute k.a. initsialiseeringuid mis on defineeritud maagilises meetodis __init__ ehk konstruktoris.

Konstruktori pärimine¶

Näide milles päritakse konstruktor ehk maagiline meetod __init__ ilma selle andmete struktuuri ja sisu muutmata. Me teame, et saame kasutada klassi nime viitamaks selle meetoditele, seega:

In [13]:
class Inimene:  # Superklass.
    
    def __init__(self, nimi, perenimi, sugu): 
        self.nimi = nimi
        self.perenimi = perenimi
        self.sugu = sugu  


class Tudeng(Inimene):  # Alamklass.
    
    def __init__(self, nimi, perenimi, sugu):
        # Viitan konstruktorile läbi superklassi nime.
        Inimene.__init__(self, nimi, perenimi, sugu)  # NB! Argument self.


Juulia = Inimene('Juulia', 'Tipikas', 'naine')
Juulius = Tudeng('Juulius', 'Tipikas', 'mees')  # Initsialiseering töötab.

print(Juulia.sugu)
print(Juulius.sugu)  # Initsialiseering töötab.
naine
mees

Konstruktori pärimine ja sisseehitatud funktsioon super¶

Sisseehitatud funktsioon super võimaldab viidata superklassi meetoditele alamklassi seest, k.a. konstruktorile ilma, et sa peaksid klassi nimele ilmutatud kujul viitama seda nimepidi kutsudes. Funktsiooni super kasutame peamiselt initsialiseeringute pärimisel. Lisaks kasutatakse seda ka transitiivsel konstruktorite pärimisel ülevaate hoidmiseks.

In [14]:
class Inimene:  # Superklass.
    
    def __init__(self, nimi, perenimi, sugu): 
        self.nimi = nimi
        self.perenimi = perenimi
        self.sugu = sugu  


class Tudeng(Inimene):  # Alamklass
    
    def __init__(self, nimi, perenimi, sugu):
        super().__init__(nimi, perenimi, sugu)  # NB! Argument self puudub.


Juulia = Inimene('Juulia', 'Tipikas', 'naine')
Juulius = Tudeng('Juulius', 'Tipikas', 'mees')  # Initsialiseering töötab.

print(Juulia.sugu)
print(Juulius.sugu)  # Initsialiseering töötab.
naine
mees

Konstruktori pärimine ja alamklassi objekti laiendamine¶

Kui soovime alamklassis laiendada initsialiseeringut, nt. ksutada uusi objekti muutujaid, saame seda teha järgmiselt:

  1. Initsialiseerime kasutades superklassi nime.
In [15]:
class Inimene:
    
    def __init__(self, nimi, perenimi, sugu): 
        self.nimi = nimi
        self.perenimi = perenimi
        self.sugu = sugu 


class Tudeng(Inimene):
    
    def __init__(self, nimi, perenimi, sugu, iq):
        Inimene.__init__(self, nimi, perenimi, sugu)
        self.iq = iq  # Lisan uue objekti muutuja.


Juulia = Inimene('Juulia', 'Tipikas', 'naine')
Juulius = Tudeng('Juulius', 'Tipikas', 'mees', 100)

print(Juulia.sugu)
print(Juulius.sugu)
print(Juulius.iq)
naine
mees
100
  1. Initsialiseerime kasutades funktsiooni super. Kasutades funktsiooni super saame sama tulemuse.
In [16]:
class Inimene:
    
    def __init__(self, nimi, perenimi, sugu): 
        self.nimi = nimi
        self.perenimi = perenimi
        self.sugu = sugu 
        

class Tudeng(Inimene):
    
    def __init__(self, nimi, perenimi, sugu, iq):
        super().__init__(nimi, perenimi, sugu)
        self.iq = iq  # Lisan uue objekti muutuja.


Juulia = Inimene('Juulia', 'Tipikas', 'naine')
Juulius = Tudeng('Juulius', 'Tipikas', 'mees', 100)

print(Juulia.sugu)
print(Juulius.sugu)
print(Juulius.iq)
naine
mees
100

Superklassi meetodile viitamine alamklassi seest ja funktsioon super¶

Meetodite pärimine on meile tuttav juba eelmise nädala loengust. Me teame, et pärime kõik superklassi meetodid seda pärides.

Lisame klassile Inimene meetodi to_string mis prindib objekti andmed. Pärime selle klassi alamklassi nimega Tudeng. Alamklassis Tudeng viitame meetodile to_string ja teeme selle väljakutse (võid ka muid asju teha) kasutades superklassi nime ja funktsiooni super:

In [17]:
class Inimene:
    
    def __init__(self, nimi, perenimi, sugu): 
        self.nimi = nimi
        self.perenimi = perenimi
        self.sugu = sugu    

    def to_string(self):
        print(f'Nimi on {self.nimi} {self.perenimi}.')


class Tudeng(Inimene):
    
    def __init__(self, nimi, perenimi, sugu):
        super().__init__(nimi, perenimi, sugu)

    def meetod_1(self):
        Inimene.to_string(self)  # Viide meetodile kasutades klassi nime.

    def meetod_2(self):
        super().to_string()  # Viide meetodile kasutades funktsiooni super.


Juulius = Tudeng('Juulius', 'Tipikas', 'mees')

Juulius.to_string()  # Superklassis defineeitud ja päritud.
Juulius.meetod_1()  # Superklassis defineeitud.
Juulius.meetod_2()  # Superklassis defineeitud.
Nimi on Juulius Tipikas.
Nimi on Juulius Tipikas.
Nimi on Juulius Tipikas.

Pesastatud klassid¶

Klasse nii nagu kõiki muid Pythoni blokke on võimalik üksteise sisse pesastada. All on näide klassist mis defineerib objekti tudeng kellega on seotud objekt mobiiltelefon ning kes saab sellega uhkustada ja helistada etteantud numbrile.

Märkus 3: Alloleva näite saab lahendada ka kasutades kahte eraldi klassi.

In [18]:
class Tudeng:
    
    def __init__(self, nimi, perenimi):
        self.nimi = nimi
        self.perenimi = perenimi
        self.mobiiltelefon = self.Mobiil()  # Loon objekti viidates klassile Mobiil. NB! sulud.     

    class Mobiil:
        
        def __init__(self):
            self.tootja = "LG"
            self.mudel = 'LM50PM'
            self.number = 12345678
        
        def uhkusta(self):
            return 'Mul on {} {} ja minu telefoninumber on {}.'.format(self.tootja, self.mudel, self.number)
        
        def helista(self, number):
            print('Helistamine numbrile {} toimub.'.format(number))


tudeng = Tudeng('Juulius', 'Tipkas')

print(tudeng.mobiiltelefon.tootja)
print(tudeng.mobiiltelefon.mudel)
print(tudeng.mobiiltelefon.number)

tudeng.mobiiltelefon.helista(112)  # Läbi konstruktori initsialiseerimise.
print(tudeng.mobiiltelefon.uhkusta())  # Läbi konstruktori initsialiseerimise.

mobiil = Tudeng.Mobiil()  # Pesastatud klassiga objekti loomine.
print(mobiil)
LG
LM50PM
12345678
Helistamine numbrile 112 toimub.
Mul on LG LM50PM ja minu telefoninumber on 12345678.
<__main__.Tudeng.Mobiil object at 0x137ed1f10>

Vajadusel võime pesastatud meetodite väljakutseid lühendada luues pesastava klassi skoopi meetodid mis viitavad pesastatud klassi lokalse skoobi meetoditele:

In [19]:
class Tudeng:
    
    def __init__(self, nimi, perenimi):
        self.nimi = nimi
        self.perenimi = perenimi
        self.mobiiltelefon = self.Mobiil()   
    
    def uhkusta(self):  # Tekitasin viite pesastatud meetodi objektile.
        return self.mobiiltelefon.uhkusta()
    
    def helista(self, num):  # Tekitasin viite pesastatud meetodile.
        return self.mobiiltelefon.helista(num)

    class Mobiil:
        
        def __init__(self):
            self.tootja = "LG"
            self.mudel = 'LM50PM'
            self.number = 12345678
        
        def uhkusta(self):
            return 'Mul on {} {} ja minu telefoninumber on {}.'.format(self.tootja, self.mudel, self.number)
        
        def helista(self, number):
            print('Helistamine numbrile {} toimub.'.format(number))


tudeng = Tudeng('Juulius', 'Tipkas')

print(tudeng.mobiiltelefon.tootja)
print(tudeng.mobiiltelefon.mudel)
print(tudeng.mobiiltelefon.number)

tudeng.mobiiltelefon.helista(112)  # Läbi konstruktori initsialiseerimise.
tudeng.helista(911)  # Taustal kasutame loodud viidet.

print(tudeng.mobiiltelefon.uhkusta())  # Läbi konstruktori initsialiseerimise.
print(tudeng.uhkusta())  # Taustal kasutame loodud viidet.
LG
LM50PM
12345678
Helistamine numbrile 112 toimub.
Helistamine numbrile 911 toimub.
Mul on LG LM50PM ja minu telefoninumber on 12345678.
Mul on LG LM50PM ja minu telefoninumber on 12345678.

Kapseldamine¶

Teatavasti saame klassi- ja objekti muutujatele ligi globaalses skoobis kuna class-blokk ei kapselda muutujaid (see pole def-blokk). Sellist käitumist saab muuta kapseldades muutujaid ja meetodeid.

Näide kapseldamata klassimuutujatest, objekti muutujatest ja meetodist ehk avalikest klassimuutujatest, objekti muutujatest ja meetodist:

In [20]:
class A:
    cls_A_a = 1
    cls_A_b = 22

    def __init__(self, obj_a, obj_b):
        self.obj_a = obj_a
        self.obj_b = obj_b

    def cls_A_method(self):
        return 'obj_a = {} and obj_b = {}'.format(self.obj_a, self.obj_b)


obj = A(3, 44)

print(A.cls_A_b)  # Klassimuutuja.
print(obj.obj_b)  # Objekti muutuja.
print(obj.cls_A_method())  # Meetodi väljakutse.
print(A.cls_A_method(obj))  # Meetodi väljakutse läbi klassi.
22
44
obj_a = 3 and obj_b = 44
obj_a = 3 and obj_b = 44

Eelnäidatud kood on täiesti hea kui sa kodeerid enda jaoks ja kasutad klassi nagu sa oled seda enda peas ette kujutanud. Sinu programmi võib aga ksutada keegi teine. Kuid klassi kasutaja, kes pole klassi programmeerija mõtetega tuttav, võib käituda näiteks järgmiselt:

In [21]:
class A:
    cls_A_a = 1
    cls_A_b = 22

    def __init__(self, obj_a, obj_b):
        self.obj_a = obj_a
        self.obj_b = obj_b

    def cls_A_method(self):
        return 'obj_a = {} and obj_b = {}'.format(self.obj_a, self.obj_b)


obj = A(3, 44)

print(obj.cls_A_method())

# Saame klassi- ja objekti muutujad vabalt üle kirjutada.
A.cls_A_b = 'Häkitud number.'
obj.obj_b = 'Häkitud objekti muutuja.'
print(A.cls_A_b)
print(obj.obj_b)

# See omakorda muudab meetodite käitumise.
print(obj.cls_A_method())

# Võime meetodi hoopis hävitada.
obj.cls_A_method = 'Häkitud meetod.'

print(obj.cls_A_method)  # Väljakutse kujul obj.cls_A_method() võimatu.
obj_a = 3 and obj_b = 44
Häkitud number.
Häkitud objekti muutuja.
obj_a = 3 and obj_b = Häkitud objekti muutuja.
Häkitud meetod.

Selleks, et eelolevat olukorda vältida saame klassi atribuudid kapseldada ehk muuta need privaatseks eesmärgiga kaitsta neid globaalse skoobi juurdepääsu eest. Mäletatavasti kapseldav skoop lokaliseeris muutujad kasutamiseks ainult sellest skoobist. Meetodi kapseldamisel jääb see kasutamiseks ainult klassi bloki sees ja seest.

Atribuute kapseldame kasutades kahte alakriipsu muutuja või meetodi nime ees, nt. foo $\to$ __foo, bar() $\to$ __bar() jne.:

In [22]:
class A:
    cls_A_a = 1     # Avalik klassimuutuja.
    __cls_A_b = 22  # Kapseldatud ehk privaatne klassimuutuja.

    def __init__(self, obj_a, obj_b):
        self.obj_a = obj_a    # Avalik objekti muutuja.
        self.__obj_b = obj_b  # Kapseldatud ehk privaatne objekti muutuja.

    def __cls_A_method(self):  # Kapseldatud ehk privaatne meetod.
        return 'obj_a = {} and obj_b = {}.'.format(self.obj_a, self.__obj_b)

    def data(self):  # Avalik meetod.
        obj_b_id = id(self.__obj_b)
        return 'obj_b = {}, obj_b id = {}.'.format(self.__obj_b, obj_b_id)


obj = A(3, 44)

print(obj.data())
obj_b = 44, obj_b id = 4344999312.

Väljakutsed kujul:

print(obj.obj_b)              # Juurdepääs puudub.
print(obj.__obj_b)            # Juurdepääs puudub.
print(obj.cls_A_method)       # Juurdepääs puudub.
print(obj.cls_A_method())     # Juurdepääs puudub.
print(obj.__cls_A_method())   # Juurdepääs puudub.
print(A.cls_A_b)              # Juurdepääs puudub.
print(A.cls_A_method(obj))    # Juurdepääs puudub.
print(A.__cls_A_method(obj))  # Juurdepääs puudub.

tõstatavad erisuse AttributeError. Näiteks:

In [23]:
print(obj.obj_b)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[23], line 1
----> 1 print(obj.obj_b)

AttributeError: 'A' object has no attribute 'obj_b'

NB! Kapseldatud atribuutidege klassi puhul näitab funktsioon dir kapseldamata ehk avalikke atrubuute loendi lõpus:

In [24]:
print(dir(A))
['_A__cls_A_b', '_A__cls_A_method', '__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__', 'cls_A_a', 'data']

Usinam tudeng võis juba märgata, et globaalses skoobis saame luua uusi objekte (erinev id) kasutades kapseldatud atribuutide nimesid. Need kehtivad ainult globaalses skoobis ja ei mõjuta klassi atribuute. Kontrollime ja vaatame kas väljakutse id(obj.obj_b) väärtus erined klassisisesest väärtusest:

In [25]:
obj.obj_b = 'Häkitud muutuja'  # Uus objekt.
obj.__obj_b = 'Häkitud __muutuja.'  # Uus objekt.
obj.cls_A_method = 'Häkitud meetod.'  # Uus objekt.
obj.__cls_A_method = 'Häkitud __meetod.'  # Uus objekt.

print(obj.obj_b, 'id =', id(obj.obj_b), 'erineb kapseldatud muutuja omast.')
print(obj.__obj_b)
print(obj.cls_A_method)
print(obj.__cls_A_method)
Häkitud muutuja id = 5232907280 erineb kapseldatud muutuja omast.
Häkitud __muutuja.
Häkitud meetod.
Häkitud __meetod.

Kapseldatud muutuja on tõesti peidetud globaalse skoobi eest, veel üks näide lisaks infole eelmises lõigus:

In [26]:
class Auto:

    def __init__(self):
        self.__hind = 100

    def müü_auto(self):
        print(f"Auto müüdud hinnaga {self.__hind}.")

    def määra_hind(self, hind):
        self.__hind = hind


mersu = Auto()

# Müüme vaikimisi hinnaga.
mersu.müü_auto()

# (Püüan) muuta hinda ja müün.
mersu.__hind = 5000
mersu.müü_auto()  # NB! Ei õnnestu!

# Määran uue hinna läbi meetodi määra_hind ja müün.
mersu.määra_hind(5000)
mersu.müü_auto()
Auto müüdud hinnaga 100.
Auto müüdud hinnaga 100.
Auto müüdud hinnaga 5000.

Lisa: Näiteid kodus uurimiseks¶

Siin viidatud lisas esitame erinevaid näiteid klasside defineerimise ja nende kasutamise kohta.

















☻   ☻   ☻