21. diel - Magické metódy v Pythone druhýkrát
V minulej lekcii, Magické metódy v Pythone , sme sa venovali magickým metódam.
V dnešnom tutoriáli objektovo orientovaného programovania v Pythone budeme pokračovať v štúdiu magických metód.
Pokročilé dunder metódy
Zameriame sa na atribútové a indexové operácie, volanie objektov a správu inštancií.
Atribútové a indexové operácie
Tieto metódy sa v Pythone používajú na manipuláciu s atribútmi a indexovanie objektov. Sú kľúčové pre pokročilé programovanie a umožňujú nám vytvárať flexibilnejšie a dynamickejšie objekty.
Metóda __getattr__()
Metóda __getattr__()
sa volá, keď sa pokúšame získať
prístup k atribútu, ktorý v objekte nie je definovaný. Je užitočná na
vytváranie objektov s dynamickými atribútmi alebo na implementáciu "proxy"
objektov, ktoré delegujú prístup k atribútom na iné objekty. Ukážme si
príklad:
class FlexibilniObjekt:
def __init__(self):
self._atributy = []
def __getattr__(self, name):
# Hledáme atribut podle jména
for nazev, hodnota in self._atributy:
if nazev == name:
return hodnota
# Pokud nebyl nalezen, vytvoříme nový
hodnota = f"Dynamicky vytvořený atribut '{name}'"
self._atributy.append((name, hodnota))
return hodnota
class ProxyLogger:
def __init__(self, cilovy_objekt):
self._cilovy_objekt = cilovy_objekt
def __getattr__(self, name):
print(f"Log: Přístup k atributu '{name}'")
return getattr(self._cilovy_objekt, name)
# Demonstrace FlexibilniObjekt
objekt = FlexibilniObjekt()
print(objekt.neexistujici_atribut) # Dynamicky vytvoří a vrátí hodnotu atributu
objekt.neexistujici_atribut = "Nová hodnota"
print(objekt.neexistujici_atribut) # Vrátí aktualizovanou hodnotu
# Demonstrace ProxyLogger
skoda = FlexibilniObjekt()
proxy = ProxyLogger(skoda)
print(proxy.model) # Loguje a dynamicky vytvoří atribut 'model'
skoda.model = "Octavia"
print(proxy.model) # Loguje a vypíše 'Octavia'
V príklade trieda FlexibilniObjekt
používa metódu
__getattr__()
na dynamické vytváranie atribútov. Pri
inicializácii inštancie FlexibilniObjekt
sa vytvorí súkromný
zoznam _atributy
pre uchovávanie atribútov. Keď sa pokúsime
prvýkrát pristúpiť k neexistujici_atribut
, volá sa metóda
__getattr__()
. Táto metóda najskôr skontroluje, či je atribút
už v zozname _atributy
. Ak nie, vytvorí pre neho predvolenú
hodnotu a uloží ju do _atributy
.
Trieda ProxyLogger
slúži ako proxy, ktorá loguje prístupy k
atribútom na cieľovom objekte. Pri inicializácii ProxyLogger
sa
odovzdá cieľový objekt a uloží sa do atribútu _cilovy_objekt
.
Keď sa pokúsime pristúpiť k atribútu inštancie ProxyLogger
,
Python najprv skontroluje, či tento atribút existuje priamo v inštancii
ProxyLogger
. Ak nie, dôjde k aktivácii metódy
__getattr__()
. Tá pomocou funkcie getattr()
odovzdá
prístup k atribútom na objekt _cilovy_objekt
. Funkcia
getattr()
je štandardná vstavaná funkcia v Pythone, ktorá
umožňuje programu pristúpiť k atribútu objektu podľa jeho názvu (tu ako
reťazec). V tomto prípade getattr()
používame na pristúpenie k
atribútu na objekte _cilovy_objekt
, ktorý inštancia
ProxyLogger
zastupuje. Tým sa teda dosahuje to, že pokiaľ
atribút nie je nájdený priamo v inštancii ProxyLogger
,
__getattr__()
ho hľadá v objekte _cilovy_objekt
. To
je základný princíp fungovania proxy objektu – sprostredkovanie prístupu k
atribútom iného objektu.
Po spustení kódu vytvoríme inštanciu triedy FlexibilniObjekt
v objekt
a pokúsime sa pristúpiť k
neexistujici_atribut
. Tento atribút je následne dynamicky
vytvorený a vrátený. Keď tento atribút neskôr zmeníme, metóda
__getattr__()
vráti jeho aktualizovanú hodnotu.
Ďalej vytvoríme inštanciu triedy FlexibilniObjekt
v
skoda
a obalíme ju pomocou triedy ProxyLogger
. Keď
potom pristúpime k atribútu proxy.model
, dôjde k logovaniu a
vytvoreniu atribútu model
v inštancii triedy
FlexibilniObjekt
(pretože model
ešte neexistuje).
Potom nastavíme atribút skoda.model
na Octavia
.
Tento krok sa deje priamo na inštancii FlexibilniObjekt
, nie cez
ProxyLogger
. Pri ďalšom prístupe k proxy.model
dôjde opäť k logovaniu, ale tentoraz sa už vráti hodnota
Octavia
.
V tomto príklade je dôležité si uvedomiť, že __getattr__()
v inštancii FlexibilniObjekt
sa spúšťa len, keď atribút
neexistuje, zatiaľ čo __getattr__()
v inštancii
ProxyLogger
sa aktivuje pri každom pokuse o prístup k atribútu,
ktorý nie je priamo v ProxyLogger
.
Volanie objektov
Python umožňuje volať inštancie tried ako funkcie. Na tento zaujímavý mechanizmus má priamo vstavanú metódu.
Metóda __call__()
Keď v rámci triedy definujeme metódu __call__()
, inštanciu
tejto triedy potom možno používať podobne ako funkciu. Toto správanie je
užitočné v situáciách, keď chceme, aby náš objekt mal nejakú hlavnú
funkcionalitu, ktorú je možné vyjadriť pomocou volania. Pozrime sa na
príklad. Predstavme si, že máme triedu Scitac
, ktorá má za
úlohu sčítať čísla. Metódu __call__()
implementujeme tak,
aby prijímala ľubovoľný počet čísel a vracala ich súčet:
class Scitac:
def __call__(self, *args):
return sum(args)
# Použití
scitac = Scitac()
vysledek = scitac(1, 2, 3, 4) # Vrací 10
print(f"Výsledek součtu je: {vysledek}")
Prečo používať __call__()
:
- prehľadnosť a intuitívnosť - metóda vytvára jasne definovanú hlavnú funkciu objektu, čo je intuitívnejšie pre ostatných vývojárov, ktorí náš kód používajú.
- zapuzdrenie funkcionality - tento mechanizmus umožňuje zapúzdriť funkcionalitu a súvisiaci stav do jedného objektu. Objekt tiež dokáže udržiavať stav medzi jednotlivými volaniami.
- flexibilita v návrhu – mechanizmus poskytuje ďalšiu vrstvu abstrakcie a umožňuje návrhové vzory ako sú Command alebo Strategy, kde sú objekty používané ako reprezentácia operácií alebo stratégií.
V lekcii Hracia
kocka v Pythone - Zapúzdrenie a konštruktor hneď na začiatku kurzu sme
sa naučili vytvárať inštancie tried pomocou konštruktorov. Povedali sme si,
že na vytvorenie novej inštancie máme k dispozícii dve metódy a zamerali sa
na metódu __init__()
. Teraz sa pozrieme na tú druhú. Súčasne
si trochu ujasníme zjednodušenie, ktorého sme sa na začiatku kurzu
dopustili.
Metóda __new__()
Metóda __new__()
v Pythone je špeciálna statická metóda,
ktorá je zodpovedná za vytváranie nových inštancií tried. Na rozdiel od
metódy __init__()
, ktorá inicializuje už vytvorenú inštanciu
(toto je to zjednodušenie, praktický význam ale v podstate nemá), metóda
__new__()
sa vlastne podieľa na samotnom procese vytvárania
každej inštancie. Táto metóda je volaná ešte pred __init__
a
je obvykle používaná v pokročilejších scenároch, ako je kontrola nad
procesom vytvárania objektov alebo implementácia návrhových vzorov ako je Singleton.
Majme triedu Jedinacek
, ktorá využíva __new__()
na zabezpečenie, že bude vytvorená iba jedna inštancia tejto triedy:
class Jedinacek:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
# Použití
objekt1 = Jedinacek()
objekt2 = Jedinacek()
print(objekt1 is objekt2) # Vrací True, obě proměnné odkazují na stejnou instanci
V tomto príklade __new__()
kontroluje, či už existuje
inštancia triedy Jedinacek
. Ak áno, vracia túto existujúcu
inštanciu namiesto vytvárania novej. Predvedený kód je príkladom
implementácie návrhového vzoru Singleton. Vzor je často používaný v
situáciách, kde je potrebné, aby objekt bol zdieľaný naprieč rôznymi
časťami programu, ale zároveň chceme mať istotu, že existuje iba jedna
jeho inštancia.
Tento vzor je potrebné používať opatrne, pretože často vedie k problémom s dizajnom softvéru, ako sú ťažkosti pri testovaní alebo porušení princípov ako je oddelenie závislostí (dependency separation).
Užívateľsky definované kontajnery
Python nám umožňuje vytvárať vlastné kontajnerové triedy, ktoré sa chovajú podobne ako štandardné dátové štruktúry. Teda ako zoznamy alebo nám ešte neznáme slovníky a množiny. Používajú sa na to práve dunder metódy. A kto by nesúhlasil s tým, že vytvoriť si vlastný zoznam je fakt chladné:-D
Nemá príliš veľký zmysel popisovať tieto metódy samostatne. Lepšie bude ukázať si súhrnný príklad:
class MujSeznam:
def __init__(self):
self._data = [] # Inicializace interního seznamu pro ukládání dat
def __len__(self):
# Vrací počet prvků v seznamu
return len(self._data)
def __getitem__(self, index):
# Vrací prvek na zadaném indexu
return self._data[index]
def __setitem__(self, index, value):
# Nastaví hodnotu prvku na zadaném indexu
self._data[index] = value
def __delitem__(self, index):
# Odstraní prvek na zadaném indexu
del self._data[index]
def pridat(self, value):
# Přidává nový prvek na konec seznamu
self._data.append(value)
# Použití třídy
muj_seznam = MujSeznam()
muj_seznam.pridat(1) # Přidá 1
muj_seznam.pridat(2) # Přidá 2
muj_seznam.pridat(3) # Přidá 3
print(len(muj_seznam)) # Vypíše 3 (délku seznamu)
print(muj_seznam[1]) # Vypíše 2 (prvek na indexu 1)
muj_seznam[1] = 20 # Nastaví prvek na indexu 1 na 20
del muj_seznam[0] # Odstraní prvek na indexu 0
V tomto príklade trieda MujSeznam
poskytuje základné funkcie
štandardného zoznamu. Metóda __len__()
vracia počet prvkov,
__getitem__()
slúži na získanie prvku na zadanom indexe,
__setitem__()
umožňuje nastavenie hodnoty prvku a
__delitem__()
slúži na odstránenie prvku.
Výhodou oproti štandardnému zoznamu je, že náš vlastný kontajner je ľubovoľne rozšíriteľný o ďalšie metódy a funkcionality.
Týmto spôsobom dokážeme vytvárať triedy, ktoré sa chovajú ako štandardné dátové štruktúry, ale s pridanou funkcionalitou alebo zmeneným správaním, ktoré vyhovuje špecifickým potrebám našich aplikácií.
Vlastné iterátory a generátory
Vlastné iterátory a generátory sú v Pythone využívané na vytvorenie
objektov, ktoré môžu byť prechádzané pomocou cyklu for
.
Metódy na to máme dve, konkrétne __iter__()
a
__next__()
. Táto téma je jedným z tých, ktoré majú blízko ku
kolekciám, kde ho v kurze Kolekcia v Pythone
dôkladne preberieme.
Správa pamäte a Garbage Collector
V Pythone sa správa pamäte a odstraňovanie nepotrebných objektov obvykle
vykonáva automaticky prostredníctvom mechanizmu zvaného Garbage Collector.
Python však poskytuje aj špeciálnu metódu __del__()
, ktorá
umožňuje definovať správanie objektu pri jeho odstraňovaní.
Metóda __del__()
Metóda __del__()
je tzv. deštruktor. Je volaná, keď je
objekt na ceste k odstráneniu (tj keď naň neexistujú žiadne ďalšie
odkazy). Využíva sa na uvoľnenie externých zdrojov alebo na vykonanie
nejakého čistenia (napr. uzavretie súborov alebo sieťových spojení).
Avšak doba, kedy je __del__()
zavolaná, nie je pevne
daná. Python garbage collector beží podľa vlastného harmonogramu a
tak môže byť objekt odstránený ihneď po strate odkazu naň, alebo až po
dlhšej dobe. Metóda by sa preto nemala používať pre kód, na ktorého
spustení v konkrétnom okamihu záleží. Pozrime sa na príklad:
class Zaznam:
def __init__(self, hodnota):
self.hodnota = hodnota
print(f"Vytvářím záznam: {hodnota}")
def __del__(self):
print(f"Mažu záznam: {self.hodnota}")
# Příklad použití
zaznam = Zaznam("důležitý údaj")
# Další kód
del zaznam # Zde může, ale nemusí dojít k okamžitému volání __del__()
To je pre túto lekciu všetko:-)
V nasledujúcom kvíze, Kvíz - Dekorátory, vlastnosti a magické metódy v Pythone, si vyskúšame nadobudnuté skúsenosti z predchádzajúcich lekcií.