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 FlexibleObject:
def __init__(self):
self._attributes = []
def __getattr__(self, name):
# Searching for an attribute by name
for attribute_name, value in self._attributes:
if attribute_name == name:
return value
# If not found, create a new one
value = f"Dynamically created attribute '{name}'"
self._attributes.append((name, value))
return value
class ProxyLogger:
def __init__(self, target_object):
self._target_object = target_object
def __getattr__(self, name):
print(f"Log: Accessing attribute '{name}'")
return getattr(self._target_object, name)
# Demonstration of FlexibleObject
object = FlexibleObject()
print(object.nonexistent_attribute) # Dynamically creates and returns the attribute value
object.nonexistent_attribute = "New value"
print(object.nonexistent_attribute) # Returns the updated value
# Demonstration of ProxyLogger
skoda = FlexibleObject()
proxy = ProxyLogger(skoda)
print(proxy.model) # Logs and dynamically creates 'model' attribute
skoda.model = "Octavia"
print(proxy.model) # Logs and prints 'Octavia'
V príklade trieda FlexibleObject
používa metódu
__getattr__()
na dynamické vytváranie atribútov. Pri
inicializácii inštancie FlexibleObject
sa vytvorí súkromný
zoznam _attributes
pre uchovávanie atribútov. Keď sa pokúsime
prvýkrát pristúpiť k nonexistent_attribute
, volá sa metóda
__getattr__()
. Táto metóda najskôr skontroluje, či je atribút
už v zozname _attributes
. Ak nie, vytvorí pre neho predvolenú
hodnotu a uloží ju do _attributes
.
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 _target_object
.
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 _target_object
. 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 _target_object
, 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 _target_object
. 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 FlexibleObject
v
object
a pokúsime sa pristúpiť k
nonexistent_attribute
. 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 FlexibleObject
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
FlexibleObject
(pretože model
ešte neexistuje).
Potom nastavíme atribút skoda.model
na Octavia
.
Tento krok sa deje priamo na inštancii FlexibleObject
, 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 FlexibleObject
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 Adder
, 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 Adder:
def __call__(self, *args):
return sum(args)
# Usage
adder = Adder()
result = adder(1, 2, 3, 4) # Returns 10
print(f"The result of the sum is: {result}")
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í.
Správa inštancií
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 Singleton
, ktorá využíva __new__()
na zabezpečenie, že bude vytvorená iba jedna inštancia tejto triedy:
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
# Usage
object1 = Singleton()
object2 = Singleton()
print(object1 is object2) # Returns True, both variables refer to the same instance
V tomto príklade __new__()
kontroluje, či už existuje
inštancia triedy Singleton
. 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é
Nemá príliš veľký zmysel popisovať tieto metódy samostatne. Lepšie bude ukázať si súhrnný príklad:
class MyList:
def __init__(self):
self._data = [] # Initialization of internal list to store data
def __len__(self):
# Returns the number of elements in the list
return len(self._data)
def __getitem__(self, index):
# Returns the element at the specified index
return self._data[index]
def __setitem__(self, index, value):
# Sets the value of the element at the specified index
self._data[index] = value
def __delitem__(self, index):
# Removes the element at the specified index
del self._data[index]
def add(self, value):
# Adds a new element to the end of the list
self._data.append(value)
# Using the class
my_list = MyList()
my_list.add(1) # Adds 1
my_list.add(2) # Adds 2
my_list.add(3) # Adds 3
print(len(my_list)) # Prints 3 (length of the list)
print(my_list[1]) # Prints 2 (element at index 1)
my_list[1] = 20 # Sets the element at index 1 to 20
del my_list[0] # Removes the element at index 0
V tomto príklade trieda MyList
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 Kolekcie 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 Record:
def __init__(self, value):
self.value = value
print(f"Creating record: {value}")
def __del__(self):
print(f"Deleting record: {self.value}")
# Usage
record = Record("important data")
# Additional code
del record # Here, __del__() may or may not be called immediately
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í.