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

Lisa: Näiteid iseseisvalt uurimiseks¶

Enda loodud andmetüüpide kasutamine klassides¶

Loengutes 12 ja 13 oleme näidatud enda loodud objektide ja klasside kui andmetüüpide kasutamist teistes klassides suhteliselt minimaalselt. Järgmises kaks näidet püüavad seda olukorda parandada.

Hüpoteetilise foorumi kasutajad ja postitused¶

Näide programmist mis haldab interneti foorumi kasutajaid ja postitusi. Programmis opereeritakse andmetüüpidega User (foorumi kasutaja), AdminUser (andministraatori õigustega kasutaja) ja Post (postiused ise enda andmetega):

In [1]:
class User:
    users = {}
    
    def __init__(self, username):
        self.username = username
        User.users[username] = self
        
    def __repr__(self):  # Kui meetodit __str__ pole ülekirjutatud kasutab igal pool seda.
        #return f"{type(self).__name__} --> {self.username}"  # Nii saab ka.
        return f"{self.__class__.__name__} --> {self.username}"
           
    def add_post(self, title, body):
        return Post(self, title, body)  # Väljastab andmetüübi Post objekti.
    
    def remove_post(self, id):
        post = Post.posts.get(id)
        if post and post.author == self:
            del Post.posts[id]
            return True
        else:
            return False


class AdminUser(User):
    
    def __init__(self, username):
        super().__init__(username)
        
    def remove_user(self, username):
        if username != self.username and username in self.users:
            del self.users[username]
            return True
        else:
            return False
    
    def remove_post(self, id):
        post = Post.posts.get(id)
        if post:
            del Post.posts[id]
            return True
        else:
            return False


class Post:
    posts = {}
    id_tracker = 1

    def __init__(self, author, title, body):  # Argumendi autori andmetüüp on User või AdminUser
        self.id = int(Post.id_tracker)
        self.author = author
        self.title = title
        self.body = body
        Post.posts[self.id] = self
        Post.id_tracker += 1
        
    def __repr__(self):
        return f"{self.title} by {self.author.username}"
    
    def get_post_data(self):
        return {"author": self.author.username,
                "title": self.title,
                "body": self.body}
    
    def edit_post(self, data):
        if "title" in data and "body" in data:
            title = data["title"]
            body = data["body"]
            if 2 <= len(title) <= 32 and 2 <= len(body) <= 255:
                self.title = title
                self.body = body
                return True
            else:
                return False



# Loome kasutajaid:
user1 = User("Juulius Tipikas")
user2 = AdminUser("Tiit Land")

# Kautajad kirjutavad postitusi:
post1 = user1.add_post("LOREM IPSUM", "Dolor sit amet.")
post2 = user2.add_post("JUULIUSE ELU", "Juulius Tipika elu on kurb.")
post3 = user1.add_post("TIIT LAND LAIMAB", "Miks sa laimad mind!")

# Kautajate repr:
print(user1)
print(user2)
print()

# Kõik loodud kasutajad:
print(User.users)
print()

# Kõik loodud postitused:
print(Post.posts)
print()

# Kõigi loodud postituste andmed:
print(post1.get_post_data())
print(post2.get_post_data())
print(post3.get_post_data())
print()

# AdminUser kustutab postituse post3:
print(user2.remove_post(3))
print()

# Kõik loodud postitused - kustutatud postitused:
print(Post.posts)
User --> Juulius Tipikas
AdminUser --> Tiit Land

{'Juulius Tipikas': User --> Juulius Tipikas, 'Tiit Land': AdminUser --> Tiit Land}

{1: LOREM IPSUM by Juulius Tipikas, 2: JUULIUSE ELU by Tiit Land, 3: TIIT LAND LAIMAB by Juulius Tipikas}

{'author': 'Juulius Tipikas', 'title': 'LOREM IPSUM', 'body': 'Dolor sit amet.'}
{'author': 'Tiit Land', 'title': 'JUULIUSE ELU', 'body': 'Juulius Tipika elu on kurb.'}
{'author': 'Juulius Tipikas', 'title': 'TIIT LAND LAIMAB', 'body': 'Miks sa laimad mind!'}

True

{1: LOREM IPSUM by Juulius Tipikas, 2: JUULIUSE ELU by Tiit Land}

Postituste andmete kapseldamine¶

Näide samast programmist kus postituste andmed on kapseldatud:

In [2]:
class User:
    users = {}
    
    def __init__(self, username):
        self.username = username
        User.users[username] = self
        
    def __repr__(self):  # Kui meetodit __str__ pole ülekirjutatud kasutab igal pool seda.
        #return f"{type(self).__name__} --> {self.username}"  # Nii saab ka.
        return f"{self.__class__.__name__} --> {self.username}"
           
    def add_post(self, title, body):
        return Post(self, title, body)  # Väljastab andmetüübi Post objekti.
    
    def remove_post(self, id):
        post = Post.posts.get(id)
        if post and post.author == self:
            del Post.posts[id]
            return True
        else:
            return False


class AdminUser(User):
    
    def __init__(self, username):
        super().__init__(username)
        
    def remove_user(self, username):
        if username != self.username and username in self.users:
            del self.users[username]
            return True
        else:
            return False
    
    def remove_post(self, id):
        post = Post.posts.get(id)
        if post:
            del Post.posts[id]
            return True
        else:
            return False


class Post:
    __posts = {}  # Kapseldamine.
    id_tracker = 1  # Avalik.

    def __init__(self, author, title, body):  # Argumendi autori andmetüüp on User või AdminUser
        self.__id = int(Post.id_tracker)
        self.__author = author
        self.__title = title
        self.__body = body
        Post.__posts[self.__id] = self
        Post.id_tracker += 1
        
    def __repr__(self):
        return f"{self.__title} by {self.__author.username}"
    
    def get_post_data(self):  # Avalik meetod.
        return {"author": self.__author.username,
                "title": self.__title,
                "body": self.__body}
    
    def edit_post(self, data):  # Avalik meetod.
        if "title" in data and "body" in data:
            title = data["title"]
            body = data["body"]
            if 2 <= len(title) <= 32 and 2 <= len(body) <= 255:
                self.__title = title
                self.__body = body
                return True
            else:
                return False


# Loome kasutaja:
user1 = User("Juulius Tipikas")

# Loome postituse:
post1 = user1.add_post("GREAT POST", "Lorem ipsum dolor sit amet.")
print(post1)

print(post1.get_post_data())

# Juulius muudab enda postitust ja vaatame selle sisu:
print(post1.edit_post({'title': 'GREAT EDITED POST', 'body': 'Ma armastan Tiit Landi ja tema laim teeb mind kurvaks.'}))
print(post1.get_post_data())

# Püüan otse järgi vadata tundlikke andmeid:
print(Post.id_tracker)  # Pole kapseldatud, kuna pole tundlik info.
#print(post1.body)  # --> AttributeError
#print(Post.posts)  # --> AttributeError
GREAT POST by Juulius Tipikas
{'author': 'Juulius Tipikas', 'title': 'GREAT POST', 'body': 'Lorem ipsum dolor sit amet.'}
True
{'author': 'Juulius Tipikas', 'title': 'GREAT EDITED POST', 'body': 'Ma armastan Tiit Landi ja tema laim teeb mind kurvaks.'}
2

Meetodite dekoraatorid¶

Ülejäänud lisas on esitatud erinevaid näiteid mis põhinevad tudengite objektide loomisele.

Meetodi dekoraator @classmethod¶

  • Meetod, argument self.
  • Klassimuutujale viitamisel kasutame klassi nime.
  • Klassimeetodis viitame klassimuutujale kasutades positsionaalset argumenti cls.
In [3]:
class Tudeng:
    tudengite_arv = 0
    tosta_stipp = 1.05
    
    def __init__(self, eesnimi, perenimi, stipp): 
        self.eesnimi = eesnimi
        self.perenimi = perenimi
        self.stipp = stipp
        self.email = eesnimi + '.' + perenimi + '@taltech.ee'
        Tudeng.tudengite_arv += 1
    
    def taisnimi(self):
        return '{} {}'.format(self.eesnimi, self.perenimi)
    
    def stipi_tostmine(self):  # Meetod mis tõstab stipi väärtust.
        self.stipp = int(Tudeng.tosta_stipp * self.stipp)
        #self.stipp = int(self.tosta_stipp * self.stipp)  # Nii seotud kindla tudengiga.
        return self.stipp
        
    @classmethod  # Dekoraator mis lubab esimeseks argumendiks klassi.
    def maara_stipp_tous(cls, protsent):  # NB! Argument cls.
        cls.tosta_stipp = protsent  # Klassimuutuja.


tudeng1 = Tudeng('Juulius', 'Tipkas', 200)
tudeng2 = Tudeng('Mari', 'Tamm', 100)

Tudeng.maara_stipp_tous(1.5)

print(Tudeng.tosta_stipp)  # Staatilise muutuja väärtus.
print(tudeng1.tosta_stipp)
print(tudeng2.tosta_stipp)
print(tudeng1.stipi_tostmine())  # tõstan Juuliusel
1.5
1.5
1.5
300

Klassimeetodi kasutamise näide, probleemi püstitus:

In [4]:
class Tudeng:
    
    def __init__(self, eesnimi, perenimi, stipp):
        self.eesnimi = eesnimi
        self.perenimi = perenimi
        self.stipp = stipp
        self.email = eesnimi + '.' + perenimi + '@taltech.ee'


tudeng1 = Tudeng('Juulius', 'Tipkas', 200)
tudeng2 = Tudeng('Mari', 'Tamm', 100)

# Tudengite info saabub vales formaadis,
# loome meetodi mis saab vale formaadiga hakkama.
tud3 = 'Mai-Tops-500'
tud4 = 'Maia-Topsu-200'
tud5 = 'Malle-Tamm-100'

# Käsitsi lahendus.
eesnimi, perenimi, stipp = tud3.split('-')
tudeng3 = Tudeng(eesnimi, perenimi, stipp)  # Loome objekti.
print(tudeng3.stipp)  # Töötab aga tülikas.
print(tudeng3.email)
500
Mai.Tops@taltech.ee

Püüame eelmise lõigus esitatud probleemi lahendada klassimeetodi abil (klassi seest):

In [5]:
class Tudeng:
    
    def __init__(self, eesnimi, perenimi, stipp): 
        self.eesnimi = eesnimi
        self.perenimi = perenimi
        self.stipp = stipp
        self.email = eesnimi + '.' + perenimi + '@taltech.ee'
    
    @classmethod
    def from_string(cls, vigane_tudeng):
        eesnimi, perenimi, stipp = vigane_tudeng.split('-')
        return cls(eesnimi, perenimi, stipp)  # Nimi cls ehk Tudeng.
        #return Tudeng(eesnimi, perenimi, stipp)  # Nii töötab ka.
        

tudeng1 = Tudeng('Juulius', 'Tipkas', 200)
tudeng2 = Tudeng('Mari', 'Tamm', 100)

# Tudengite info saabub nii.
tud3 = 'Mai-Tops-500'
tud4 = 'Maia-Topsu-200'
tud5 = 'Malle-Tamm-100'

tudeng3 = Tudeng.from_string(tud3)
tudeng4 = Tudeng.from_string(tud4)
tudeng5 = Tudeng.from_string(tud5)

print(tudeng1.stipp)
print(tudeng2.email)
print(tudeng3.email)
print(tudeng4.email)
print(tudeng5.email)
200
Mari.Tamm@taltech.ee
Mai.Tops@taltech.ee
Maia.Topsu@taltech.ee
Malle.Tamm@taltech.ee

Meetodi dekoraator @staticmethod¶

Staatiline meetod kus esimene positsionaalne argument (self, cls) puudub kuna meetod ei sõltu ei objektist ega klassist. Loome meetodi mis kontrollib kas stippi makstakse välja tööpäeval:

In [6]:
import datetime

class Tudeng:
    
    def __init__(self, eesnimi, perenimi, stipp):
        self.eesnimi = eesnimi
        self.perenimi = perenimi
        self.stipp = stipp
        self.email = eesnimi + '.' + perenimi + '@taltech.ee'    
    
    @staticmethod
    def on_tööpäev(päev):  # Ei sõltu klassist ega objektist.
        if päev.weekday() == 5 or päev.weekday() == 6:
            return False
        else:
            return True
        

tudeng1 = Tudeng('Juulius', 'Tipkas', 200)
tudeng2 = Tudeng('Mari', 'Tamm', 100)

kuupaev = datetime.date(2021, 1, 25)

print(Tudeng.on_tööpäev(kuupaev))
print(tudeng1.on_tööpäev(kuupaev))
print(tudeng2.on_tööpäev(kuupaev))
True
True
True

Meetodi dekoraator @property¶

Dekoraator @property lubab luua meetodi mis käitub nagu klassimuutuja. Probleemi püstitus:

In [7]:
class Tudeng:
    
    def __init__(self, eesnimi, perenimi):
        self.eesnimi = eesnimi
        self.perenimi = perenimi
        self.email = eesnimi + '.' + perenimi + '@taltech.ee'  # Luuakse init. ajal.
        #self.email = self.eesnimi + '.' + self.perenimi + '@taltech.ee'  
    
    def taisnimi(self):
        return '{} {}'.format(self.eesnimi, self.perenimi)   
    

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

tudeng1.eesnimi = 'Juuli'  # Muudan käsitsi.

print(tudeng1.eesnimi)
print(tudeng1.email)       # Probleem: nimi e-mailis ei muutunud. 
print(tudeng1.taisnimi())  # Probleem: kellegile lihtsalt ei meeldi argumendi sulge sisestada.
Juuli
Juulius.Tipkas@taltech.ee
Juuli Tipkas

Parandame e-maili addressi probleemi, selleks peame looma uue meetodi:

In [8]:
class Tudeng:
    
    def __init__(self, eesnimi, perenimi):
        self.eesnimi = eesnimi
        self.perenimi = perenimi
    
    def email(self):  # Sõltub initsialiseeritud objaktist.
        return '{}.{}@taltech.ee'.format(self.eesnimi, self.perenimi) 
    
    def taisnimi(self):
        return '{} {}'.format(self.eesnimi, self.perenimi)   
    
    
tudeng1 = Tudeng('Juulius', 'Tipkas')

tudeng1.eesnimi = 'Juuli'  # Muudetakse ka meetodi jaoks

print(tudeng1.eesnimi)
print(tudeng1.email())     # Lahendus: e-mail õige. Aga argumendi sulud ei meeldi endiselt.
print(tudeng1.taisnimi())  # Probleem: Argumendi sulud ei meeldi.
Juuli
Juuli.Tipkas@taltech.ee
Juuli Tipkas

Lahendus argumendi sulgude probleemile kasutades dekoraatorit @property:

In [9]:
class Tudeng:
    
    def __init__(self, eesnimi, perenimi):
        self.eesnimi = eesnimi
        self.perenimi = perenimi
    
    @property
    def email(self):
        return '{}.{}@taltech.ee'.format(self.eesnimi, self.perenimi) 
    
    @property
    def taisnimi(self):
        return '{} {}'.format(self.eesnimi, self.perenimi)   
    

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

tudeng1.eesnimi = 'Juuli'

print(tudeng1.eesnimi)
print(tudeng1.email)     # @property lubab muutujana välja kutsuda. Pole sulge.
print(tudeng1.taisnimi)  # @property lubab muutujana välja kutsuda.
Juuli
Juuli.Tipkas@taltech.ee
Juuli Tipkas

Pärilikkus ja funktsioon super¶

TTÜs on erinevaid tudengeid ja erinevatelt õppekavadelt.

In [10]:
class Tudeng:  # Üldine tudengi klass
    tosta_stipp = 1.05

    def __init__(self, eesnimi, perenimi, stipp): 
        self.eesnimi = eesnimi
        self.perenimi = perenimi
        self.stipp = stipp
        self.email = eesnimi + '.' + perenimi + '@taltech.ee'
    
    def taisnimi(self):
        return '{} {}'.format(self.eesnimi, self.perenimi)
    
    def stipi_tostmine(self):
        self.stipp = int(Tudeng.tosta_stipp * self.stipp)


class Fuusik(Tudeng):  # Füüsikute klass
    pass


fuusik1 = Fuusik('Juulius', 'Tipkas', 200)
fuusik2 = Fuusik('Mari', 'Tamm', 100)

print(fuusik1.email)
print(fuusik2.email)
print()

print(Fuusik.__mro__)  # Method resolution order.
print()

print(Tudeng.__dict__)  # Sõnastik klassi atribuutidest ja muust.
print()

print(dir(Tudeng))  # Avalikud atribuudid ja meetodid.
Juulius.Tipkas@taltech.ee
Mari.Tamm@taltech.ee

(<class '__main__.Fuusik'>, <class '__main__.Tudeng'>, <class 'object'>)

{'__module__': '__main__', 'tosta_stipp': 1.05, '__init__': <function Tudeng.__init__ at 0x111d89da0>, 'taisnimi': <function Tudeng.taisnimi at 0x111d89d00>, 'stipi_tostmine': <function Tudeng.stipi_tostmine at 0x111d89a80>, '__dict__': <attribute '__dict__' of 'Tudeng' objects>, '__weakref__': <attribute '__weakref__' of 'Tudeng' objects>, '__doc__': None}

['__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__', 'stipi_tostmine', 'taisnimi', 'tosta_stipp']

Tahaks muuta ainult füüsikute stippi luues uue klassi:

In [11]:
class Fuusik(Tudeng):
    tosta_stipp = 1.5  # Ülekirjutatud klassimuutuja.


fuusik1 = Fuusik('Juulius', 'Tipkas', 200)

print(fuusik1.stipp)
fuusik1.stipi_tostmine()
print(fuusik1.stipp)

tudeng1 = Tudeng('Mari', 'Tamm', 200)

print(tudeng1.stipp)
tudeng1.stipi_tostmine()  # Muutsin ainult füüsikutel.
print(tudeng1.stipp)
200
210
200
210

Lisame füüsikute __init__ meetodile asju mida klassis Tudeng pole. Näide: füüsikud oskavad programmeerida Pythoni keeles:

In [12]:
class Fuusik(Tudeng):
    tosta_stipp = 1.5

    def __init__(self, eesnimi, perenimi, stipp, progemine):
        super().__init__(eesnimi, perenimi, stipp)
        self.progemine = progemine


fuusik1 = Fuusik('Juulius', 'Tipkas', 200, 'Python')
fuusik2 = Fuusik('Mari', 'Tamm', 100, 'Java')

print(fuusik1.progemine)
print(fuusik2.progemine)

print(fuusik1.email)  # See superklassi defineeritud objekti muutuja päriti.
print(fuusik2.email)  # See superklassi defineeritud objekti muutuja päriti.
Python
Java
Juulius.Tipkas@taltech.ee
Mari.Tamm@taltech.ee

Loodud objektide kasutamine¶

Loome keemikute klassi. Keemikud on palju seltsivamad ja sotsiaalsemad kui füüsikud. Keemikud oskavad sõpru leida ja neid kaotada.

In [13]:
class Fuusik(Tudeng):  # Füüsikud
    tosta_stipp = 1.5

    def __init__(self, eesnimi, perenimi, stipp, progemine):
        super().__init__(eesnimi, perenimi, stipp)
        self.progemine = progemine
        
     
class Keemik(Tudeng):  # Keemikud pärinvad klassist Tudeng.
    
    def __init__(self, eesnimi, perenimi, stipp, sobrad = None):
        super().__init__(eesnimi, perenimi, stipp)
        if sobrad is None:
            self.sobrad = []
        else:
            self.sobrad = sobrad
            
    def lisa_sober(self, sob):
        if sob not in self.sobrad:
            self.sobrad.append(sob)
        
    def kaota_sober(self, sob):
        if sob in self.sobrad:
            self.sobrad.remove(sob)
                
    def print_sobrad(self):
        for sob in self.sobrad:
            print('--->', sob.taisnimi())  # NB! Päritud meetod.


fuusik1 = Fuusik('Juulius', 'Tipkas', 200, 'Python')
fuusik2 = Fuusik('Mari', 'Tamm', 100, 'Java')
fuusik3 = Fuusik('Marion', 'Tamme', 100, 'C++')

# Keemik on kahe füüsikuga sõber:
keemik1 = Keemik('Kalle', 'Peerets', 600, [fuusik1, fuusik2])

print(keemik1.email)
print(keemik1.stipp)
print(keemik1.eesnimi)

print('\nKeemiku sõbrad:')
keemik1.print_sobrad()

print('\nPeale sõbra lisamist:')
keemik1.lisa_sober(fuusik2)  # Ei lähe arvesse.
keemik1.lisa_sober(fuusik3)  # Uus sõber.

keemik1.print_sobrad()

print('\nPeale sõbra kaotamist:')
keemik1.kaota_sober(fuusik1)

keemik1.print_sobrad()
Kalle.Peerets@taltech.ee
600
Kalle

Keemiku sõbrad:
---> Juulius Tipkas
---> Mari Tamm

Peale sõbra lisamist:
---> Juulius Tipkas
---> Mari Tamm
---> Marion Tamme

Peale sõbra kaotamist:
---> Mari Tamm
---> Marion Tamme

















☻   ☻   ☻