Vianoce v ITnetwork sú tu! Dobí si teraz kredity a získaj až 80 % extra kreditov na e-learningové kurzy ZADARMO. Zisti viac.
Hľadáme nové posily do ITnetwork tímu. Pozri sa na voľné pozície a pridaj sa k najagilnejšej firme na trhu - Viac informácií.

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é :-D

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


 

Predchádzajúci článok
Magické metódy v Pythone
Všetky články v sekcii
Objektovo orientované programovanie v Pythone
Preskočiť článok
(neodporúčame)
Kvíz - Dekorátory, vlastnosti a magické metódy v Pythone
Článok pre vás napísal gcx11
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
(^_^)
Aktivity