17. diel - Dekorátory druhýkrát - Parametrické a triedne dekorátory
V predchádzajúcej lekcii, Dekorátory v Pythone , sme sa zoznámili s dekorátormi a vysvetlili si princíp ich použitia.
V dnešnom tutoriáli objektovo orientovaného programovania v Pythone budeme pokračovať v práci s dekorátormi. Naučíme sa ich parametrizovať a aplikovať na triedu. V závere lekcie si tému zhrnieme a ukážeme si celý postup vytvorenia dekorátora v nadväzujúcich krokoch.
Dekorátory sú náročná téma. Je preto veľmi dôležité starostlivo analyzovať všetky ukážky kódu v lekcii, skúsiť si ich vo vlastnom IDE modifikovať a neprechádzať ďalej v tutoriáli, pokiaľ kód skutočne plne nepochopíte.
Dekorátory s parametrami
Zatiaľ čo doteraz naše dekorátory prijímali ako argumenty iba funkcie, Python vie vytvoriť aj dekorátory, ktoré samy o sebe prijímajú parametre. Vďaka tomu dokážeme jednoducho vytvárať dekorátory, ktoré sa chovajú rôzne na základe poskytnutých parametrov.
Využitie dekorátorov s parametrami je veľmi často vidieť napríklad v rôznych webových frameworkoch, kde je možné konfigurovať, ako sa majú funkcie (napr. spracovanie HTTP požiadaviek) správať na základe rôznych argumentov.
Keď chceme vytvoriť dekorátor s parametrami, potrebujeme tri úrovne funkcií:
- vonkajšiu funkciu, ktorá prijíma parametre dekorátora,
- vnútornú funkciu (dekorátor), ktorá prijíma funkciu, ktorú chceme dekorovať,
- obalenú funkciu - to je tá skutočná funkcia, ktorá rozširuje správanie pôvodnej dekorovanej funkcie a je zavolaná namiesto nej.
Toto je práve tá "továreň na dekorátory" - možnosť vytvoriť dekorátor na mieru podľa našich potrieb.
Pozrime sa bližšie na kód. Náš "vonkajší" dekorátor
zprava_dekorator()
prijíma argument zprava
a vracia
skutočný dekorátor vnitrni_dekorator()
. Ten následne obaľuje
naše funkcie secti()
a nasob()
. Vďaka tomu môžeme
ľahko meniť obsah správy pre rôzne funkcie bez toho, aby sme museli meniť
samotný dekorátor. Vnútorná funkcia obalena_funkce()
pozná
hodnotu premennej zprava
iba z takzvaného vonkajšieho
kontextu, čo je ukážkou mechanizmu zvaného
closure.
Uzáver (closure)
Uzáver (closure) je funkcia, ktorá si "pamätá" svoje voľné premenné z okolitého kontextu, v ktorom bola definovaná, a dokáže k nim pristupovať aj po skončení tohto kontextu. Jednoducho povedané, closure je funkcia spoločne s nejakým zachyteným kontextom. V kóde je tento "kontext" tvorený premennými, ktoré sú dostupné v okamihu vytvorenia closure:
V tomto príklade je
vnitrni_funkce()
uzáverom, ktorý má prístup k premennej
delenec
aj po tom, čo vnejsi_funkce()
skončila.
Vysvetlenie, prečo si funkcia "pamätá" svoj kontext, je spojené s tým,
ako Python funguje "pod kapotou". Uzávery v Pythone sú realizované
prostredníctvom objektu, ktorý reprezentuje funkciu. Tento objekt obsahuje
niekoľko atribútov, ktoré uchovávajú informácie o funkcii a jej kontexte.
Jedným z týchto atribútov je __closure__
, ktorý obsahuje
referencie na voľné premenné z kontextu, kde bola funkcia
vytvorená. Keď definujeme vnorenú funkciu vo vnútri inej funkcie a táto
vnorená funkcia odkazuje na premenné z vonkajšej funkcie, Python closure
vytvorí automaticky.
Triedne dekorátory
Rovnako ako sme vytvárali dekorátory pre funkcie, budeme taktiež tvoriť dekorátory pre triedy. Triedne dekorátory obvykle pridávajú, upravujú alebo rozširujú funkcionalitu triedy.
Rovnako ako pri funkčných dekorátoroch, tak aj triedny dekorátor je funkciou, ktorá prijíma triedu ako argument a vracia upravenú alebo novú triedu. Pozrime sa na príklad:
Výhodami triednych dekorátorov sú:
- modularita - oddelíme rôzne funkcionality do rôznych dekorátorov a aplikujeme ich podľa potreby,
- opakovaná použiteľnosť - raz vytvorený dekorátor je možné použiť na viacerých triedach,
- rozšíriteľnosť - ľahko rozšírime funkcie existujúcich tried bez úpravy pôvodného kódu.
- komplexitu - rovnako ako s funkčnými dekorátormi je dôležité nepreplácať dekorátory príliš mnohými funkciami. Výsledkom budú zmätenie a komplikácie pri čítaní kódu,
- dedičnosť - dekorátor samozrejme interaguje s dedičnosťou. Pokiaľ trieda dedí z inej triedy, dekorátor môže výrazne ovplyvniť správanie potomka.
Vstavané dekorátory
Python ponúka niekoľko vstavaných dekorátorov, ktoré umožňujú rýchlo
a efektívne rozšíriť funkčnosť vašich tried a funkcií. V kurze sme sa
už zoznámili s dekorátormi @staticmethod
a
@classmethod
. S dekorátorom @property
sa zoznámime v
lekcii Vlastnosti v
Pythone. Vstavané dekorátory v Pythone uľahčujú rad bežných
programátorských úloh a umožňujú efektívnu a elegantnú implementáciu
funkcionalít. Je naozaj dôležité sa s nimi dobre zoznámiť, pretože ich
budeme často stretávať v praxi.
Vytváranie vlastných dekorátorov
Už sme si ukázali, ako dekorátory fungujú, a videli sme, ako dokážu meniť správanie funkcií bez toho, aby sme ich (tie funkcie) museli priamo upravovať. Pretože ide o pomerne náročnú tému, celú lekciu si teraz zhrnieme a pozrieme sa, ako vytvoriť vlastný dekorátor od základu.
Návrh dekorátora
Základným krokom pri vytváraní dekorátora je napísať funkciu (tj dekorátor), ktorý prijíma funkciu ako argument a vracia inú funkciu:
Parametrizácia dekorátora
Ako sme si ukázali, dekorátor vie prijímať parametre. Na to potrebujeme ďalšiu vonkajšiu funkciu, ktorá obklopí náš dekorátor:
Použitie dekorátora
Na aplikáciu dekorátora na funkciu použijeme @
syntax:
Volanie
pozdrav()
je možné bez použitia
@dekorator_s_parametry("pozdrav")
nahradiť zápisom
dekorator_s_parametry("pozdrav")(pozdrav)()
. Keď každý
náš vytvorený dekorátor dokážeme zapísať aj týmto spôsobom,
je to dobrá známka toho, že problematike dobre rozumieme.
Dekorátory sú silným nástrojom, pokiaľ sú používané správne. Umožňujú nám dodať dodatočné správanie funkciám alebo triedam v modulárnej a čitateľnej forme. Je ale veľmi dôležité dbať na to, aby kód zostal čitateľný a nesnažiť sa natlačiť za každú cenu príliš veľa funkčnosti do jedného dekorátora.
V nasledujúcej lekcii, Vlastnosti v Pythone druhýkrát - Pokročilé vlastnosti a dedenie , sa v práci s vlastnosťami zameriame na dedenie, časté chyby a vytváranie vlastných dekorátorov pre vlastnosti.