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):
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:
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
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:
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):
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:
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:
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:
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
:
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.
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:
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:
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.
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