IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

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


 

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