12. diel - Vlastnosti v Pythone
V minulej lekcii, Knižnica datetime pre Python , sme si vysvetlili dátum a čas. V dnešnom Python tutoriálu sa pozrieme na ďalšie prvky tried, ktoré ešte nepoznáme. Začnime prisľúbeným vlastnosťami.
Vlastnosti
Veľmi často sa nám stáva, že chceme mať kontrolu nad zmenami nejakého atribútu objektu zvonku. Budeme chcieť atribút nastaviť ako read-only alebo reagovať na jeho zmeny. Založme si súbor (názov Vlastnosti) a vytvorme nasledujúce triedu Študent, ktorá bude reprezentovať študenta v nejakom informačnom systéme.
class Student: def __init__(self, jmeno, pohlavi, vek): self.jmeno = jmeno self.muz = pohlavi self.vek = vek self.plnolety = (vek > 18) def __str__(self): jsem_plnolety = "jsem" if self.plnolety else "nejsem" pohlavi = "muž" if self.muz else "žena" return "Jsem {0}, {1}. Je mi {2} let a {3} plnoletý.".format( self.jmeno, pohlavi, self.vek, jsem_plnolety)
Trieda je veľmi jednoduchá, študent sa nejako volá, je nejakého pohlavia a má určitý vek. Podľa tohto veku sa nastavuje atribút plnoletý pre pohodlnejšie vyhodnocovanie plnoletosti na rôznych miestach systému. Na uloženie pohlavia používame boolean, či je študent muž. Konštruktor podľa veku určí, či je študent plnoletý. Metóda __str __ () je navrhnutá pre potreby tutoriálu tak, aby nám vypísala všetky informácie. V reáli by tam bolo pravdepodobne len meno študenta. Pomocou konstruktoru si nejakého študenta vytvorme:
s = Student("Pavel Hora", True, 20) print(s) input()
výstup:
Jsem Pavel Hora, muž. Je mi 20 let a jsem plnoletý.
Všetko vyzerá pekne, ale atribúty sú prístupné ako na čítanie, tak na zápis. Objekt teda môžeme rozbiť napríklad takto (hovoríme o nekonzistentnom vnútorným stave):
s = Student("Pavel Hora", True, 20) s.vek = 15 s.muz = False print(s) input()
výstup:
Určite musíme ošetriť, aby sa plnoletosť obnovila pri zmene veku. Keď sa zamyslíme nad ostatnými atribúty, nie je najmenší dôvod, aby sme ich taktiež umožňovali modifikovať. Študent si za normálnych okolností asi len ťažko zmení pohlavia alebo meno. Bolo by však zároveň vhodné ich vystaviť na čítanie, nemôžeme je teda iba iba nastaviť ako private. V skorších dieloch seriálu sme na tento účel používali metódy, ktoré slúžili na čítanie privátnych atribútov. Ich názov sme volili ako vrat_vek () a podobne. Na čítanie vybraných atribútov vytvoríme tiež metódy a atribúty označíme ako privátne. Trieda by novo vyzerala napr. Nasledovne:
class Student: def __init__(self, jmeno, pohlavi, vek): self.__jmeno = jmeno self.__muz = pohlavi self.__vek = vek self.__plnolety = (vek > 18) def __str__(self): jsem_plnolety = "jsem" if self.__plnolety else "nejsem" pohlavi = "muž" if self.__muz else "žena" return "Jsem {0}, {1}. Je mi {2} let a {3} plnoletý.".format( self.__jmeno, pohlavi, self.__vek, jsem_plnolety) def vrat_jmeno(self): return self.__jmeno def vrat_plnoletost(self): return self.__plnoletost def vrat_vek(self): return self.__vek def muz(self): return self.__muz def nastav_vek(self, hodnota): self.__vek = hodnota self.__plnolety = True if vek < 18: self.__plnolety = False
Metódy, čo hodnoty len vracajú, sú veľmi jednoduché. Nastavenie veku má už nejakú vnútornú logiku, pri jeho zmene musíme totiž prehodnotiť atribút plnoletá. Zaistili sme, že sa do premenných nedá zapisovať inak, ako my chceme. Máme teda pod kontrolou všetky zmeny atribútov a dokážeme na ne reagovať. Nemôže sa stať, že by nám niekto vnútorný stav nekontrolovane menil a rozbil.
Metódam na vrátenie hodnoty sa hovorí Getter a metódam pre zápis setter. Ručné písanie Getter a setter je určite veľmi zdĺhavé. Nemohol by sme si ušetriť prácu? Áno, v Pythone možno použiť kratšie a lepší zápis. Potom už nehovoríme o atribútoch, ale o vlastnostiach.
Syntax vlastnosti je veľmi podobná metóde:
@property def jmeno(self): return self.__jmeno # atribut svázaný s metodou
Vytvoríme metódu, ktorá má rovnaký názov ako požadovaný názov vlastnosti. V tele metódy vrátime atribút spojený s vlastnosťou. Metódu dekorujeme dekorátorem property. Tým sa z metódy stane vlastnosť. Dekorátory majú nasledujúcu syntax:
@jmeno_dekoratoru
def nejaka_metoda(self):
...
V skutočnosti sú dekorátory objekty podporujúce volania (napr. Funkcie, metódy alebo objekty s metódou __call __ ()), ktoré vracia upravenú (dekorovanou) verziu metódy alebo funkcie. Preto možno použiť aj nasledujúcu syntax:
puvodni_nazev_metody = jmeno_dekoratoru()
V Pythone máme privátny atribút ak nemu máme dve metódy, ktoré podľa kontextu Python volá (spozná podľa situácie, či čítame alebo zapisujeme). Keď do vlastnosti nepridáme metódu pre setter, nepôjde meniť zvnútra ani zvonku. Vlastnosť sa potom používa podobne ako normálny atribút:
print(objekt.nazev_vlastnosti) # čtení objekt.nazev_vlastnosti = hodnota # zápis
Ak si prajeme, aby sa vlastnosť vedela aj zapisovať, než len čítať, musíme si dodefinovať aj setter. Ukážme si to na našom príklade s dosiahnutím plnoletosti, ktorá sa musí po zmene veku prehodnotiť:
... @property def vek(self): return self.__vek @vek.setter def vek(self, hodnota): self.__vek = hodnota self.__plnolety = (hodnota > 18)
Sprvu je nutné si vytvoriť privátne premennú __vek, v ktorej bude hodnota v skutočnosti uložené. V Setter použijeme ďalší parameter, do ktorého sa uloží hodnota pre priradenie. S spevokol teraz pracujeme opäť rovnako, ako s atribútom. Nenápadné priradenie do veku vnútorne spustí ďalšiu logiku k prehodnoteniu atribútu plnoletá:
s.vek = 15 # nyní se změní i plnoletost
Rovnako môžeme pochopiteľne implementovať aj vlastné getter a napríklad niečo niekam logovať:
@nazev_vlastnosti.getter def nazev_vlastnosti(self): return soukromy_atribut_vlastnosti
Ak chceme, aby sa getter správal inak, tak si telo metódy upravíme. Ovšem getter musí stále niečo vracať, inak by to nebol getter.
V Pythone sa vlastnosti používajú, iba len pokiaľ sú nutné. Celý program si samozrejme môžete stiahnuť pod článkom.
__dict__ a __slots__
Ak robíme vlastnosti, tak môžeme použiť ako "skládka" atribútu buď privátne atribút (pozri vyššie), alebo verejný atribút. Pokiaľ ale použijeme verejný atribút, tak sa nám prekryjú názvy atribútu a metódy vlastnosti a program upadne do rekurzia. Ide to ale obísť. Všetky atribúty objektov sa uchovávajú v slovníku spojenom s objektom. Dostaneme sa k nemu takto:
nazev_objektu.__dict__
Potom môžeme čítať / priradiť hodnotu bez rekurzia:
nazev_objektu.__dict__["nazev_promenne"] # čtení nazev_objektu.__dict__["nazev_promenne"] = hodnota # zápis
Ak však nemáme pádny dôvod, aby bol atribút vlastnosti verejný, tak je to zbytočné. Navyše je tu jedno úskalia. Takto je možné meniť hodnoty iz vonka, bez toho, aby nám prešli kontrolou. Ošetriť to môžeme pomocou __slots__, čo by sa však nemalo vôbec použiť. Podľa dokumentácie Pythone by sme mali použiť __slots__ maximálne ak vytvárame veľké množstvo inštancií nejaké triedy a chceme za každú cenu ušetriť pamäť.
Napriek tomu sa dá k súkromným atribútom preniknúť za pomoci komolenie mien - k atribútu sa dostaneme za pomoci nasledujúcej syntaxe:
objekt.__NazevTridy_nazevatributu
Ale toto už určite nie je nevedomé získanie / zmenenie súkromného atribútu. V budúcej lekcii, Kvíz - Dedičnosť a polymorfizmus v Pythone , sa pozrieme na magické metódy objektov.
V nasledujúcom kvíze, Kvíz - Dedičnosť a polymorfizmus v Pythone, si vyskúšame nadobudnuté skúsenosti z predchádzajúcich lekcií.