16. diel - Dekorátory v Pythone
V predchádzajúcom kvíze, Kvíz - Dátum a čas v Pythone, sme si overili nadobudnuté skúsenosti z predchádzajúcich lekcií.
V nasledujúcom tutoriále objektovo orientovaného programovania v Pythone sa bližšie zoznámime s dekorátormi. Vysvetlíme si, že dekorátor je nástroj, ktorý umožňuje pridať novú funkčnosť k existujúcim objektom bez modifikácie ich štruktúry. Je to forma metaprogramovania, kde jeden kód dokáže ovplyvňovať iný kód.
Dekorátory v Pythone
Dekorátory v Pythone ponúkajú množstvo výhod, ktoré zjednodušujú a optimalizujú kód. Umožňujú nám centralizovať opakujúce sa segmenty kódu, namiesto ich mnohonásobného písania v rôznych funkciách. Okrem toho vďaka dekorátorom dokážeme pridávať špecifické funkcie, ako je meranie času behu, logovania alebo overovania prístupových práv. Navyše vďaka nim dokážeme lepšie oddeliť hlavnú logiku programu od doplnkových funkcií, čo vedie k čistejšej a ľahko čitateľnej štruktúre kódu.
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.
Základná syntax dekorátorov
Pozrime sa na príklad jednoduchého dekorátora. Základná idea dekorátora spočíva v tom, že máme funkciu (dekorátor), ktorá prijíma inú funkciu ako argument a vracia novú funkciu, ktorá rozširuje alebo mení správanie pôvodnej funkcie:
V tomto príklade:
muj_dekorator()
je náš dekorátor. Prijíma ako argument referenciu na funkciu, ktorú chceme dekorovať. Túto referenciu označujeme akofunc
(pozri nižšie).obalena_funkce()
je vnútorná funkcia, ktorá "obaľuje" dekorovanú funkciu a pridáva k nej nové správanie. Keď je táto funkcia volaná, vypíše správu pred spustením dekorovanej funkciefunc()
a ďalšiu správu po jej dokončení,- použitím syntaxe
@muj_dekorator
dekorujeme funkciuukazkova_funkce()
. V podstate to znamená, žeukazkova_funkce()
je odovzdaná ako argument (teda referencie) domuj_dekorator()
, a tak sa stáva toufunc
afunc()
v dekorátore.
Malo by nás zaraziť použitie funkcií func
a
obalena_funkce
v kóde bez zátvoriek. Ide o kľúčový
koncept v Pythone a týka sa spôsobu, ako Python zaobchádza s
funkciami ako objekty prvej triedy (first-class objects). Keď odkazujeme na
funkciu bez zátvoriek, odkazujeme sa na funkciu samotnú, nie na
výsledok jej volania. Inými slovami, funkcia je v Pythone objektom
ako každý iný (reťazec, číslo, zoznam a podobne). Preto je možné na ňu
odkazovať, odovzdávať ju ako argument inej funkcii alebo ju vrátiť ako
návratovú hodnotu z inej funkcie.
V našom kóde dekorátor pomenovaný muj_dekorator()
prijíma
ako argument odkaz na funkciu ukazkova_funkce()
. To sa stane vďaka
tomu, že funkciu ukazkova_funkce()
označíme pomocou syntaxe
@muj_dekorator
. Vnútri funkcie obalena_funkce()
potom
voláme pôvodnú funkciu ukazkova_funkce()
pomocou
func()
. Dekorátor vracia referenciu na funkciu
obalena_funkce
a nie jej výsledok, pretože nevoláme
obalena_funkce()
s koncovými zátvorkami. Keď potom voláme v
programe funkciu ukazkova_funkce()
, v skutočnosti voláme funkciu
obalena_funkce()
, ktorej kód sa vykoná.
Ukážme si to na jednoduchom príklade:
Výstup v konzole:
Dekorovanie funkcií s parametrami
Pokiaľ chceme dekorovať funkcie, ktoré prijímajú parametre, musíme v
dekorátore s týmito parametrami správne zaobchádzať. K tomu nám slúžia
*args
a **kwargs
, čo sú nám už známe konvencie na
zachytenie ľubovoľného počtu pozičných a kľúčových argumentov.
Predstavme si, že chceme vytvoriť dekorátor, ktorý loguje argumenty
funkcie:
Vďaka *args
a
**kwargs
je náš dekorátor flexibilný a dokáže dekorovať
akúkoľvek funkciu bez ohľadu na to, koľko a akých argumentov má.
Návratovými hodnotami *args
a
**kwargs
sú tuple a slovník. Tieto dátové typy ešte
nepoznáme. Zoznámime sa s nimi až v kurze Kolekcia v
Pythone. Obaja si ale kedykoľvek ľahko prevedieme na zoznam. Pre
*args
použijeme seznam_args = list(args)
a pre
**kwargs
získame zoznam dvojíc (názov argumentu, hodnota)
pomocou seznam_kwargs = list(kwargs.items())
.
Viacnásobné dekorovanie
Viacnásobné dekorovanie spočíva v aplikácii viac ako jedného dekorátora na jednu funkciu. Dekorátory sa aplikujú vo vrstvách, pričom prvý dekorátor, ktorý je aplikovaný, je ten najbližšie k funkcii. Posledný dekorátor je ten, ktorý sa nachádza najvyššie.
Pokiaľ si predstavíme dekorátory ako vrstvy obalu okolo funkcie, koncept viacnásobného dekorovania sa stáva jasnejším. Každý dekorátor pridáva ďalšiu vrstvu obalu.
Rozšírme si príklad s logovaním:
Prvým dekorátorom, ktorý
sa aplikuje, je @zmer_cas
, ktorý obaľuje funkciu
vypocitej()
. Potom sa aplikuje dekorátor
@zaloguj_volani
, ktorý obaľuje celú kombináciu
zmer_cas(vypocitej)
.
Akonáhle zavoláme funkciu vypocitej(1, 2, c=3)
, v skutočnosti
voláme:
Keď odstránime
dekorátory nad vypocitej()
a vložíme vyššie uvedenú
konštrukciu na miesto priameho volania vypocitej(1, 2, c=3)
,
dostaneme rovnaký výstup, ako pri použití dekorátorov. Zápis
zaloguj_volani(zmer_cas(vypocitej))(1, 2, c=3)
je veľmi vhodné
dôkladne preštudovať - poskytuje presný obraz toho, ako dekorátory
pracujú.
V kóde sa krok za krokom stane toto:
- funkcia
zaloguj_volani()
sa spustí a vypíše:Funkce vypocitej() byla zavolána s pozičními argumenty: (1, 2) a klíčovými argumenty: {'c': 3}.
, - funkcia
zaloguj_volani()
potom volá funkciuzmer_cas()
, ktorá meria dobu behu funkcievypocitej()
a spustí ju, - funkcia
vypocitej()
sa vykoná a vrátiVýsledek výpočtu je: 6
, - funkcia
zmer_cas()
potom dokončí meranie času a vypíše:Čas běhu funkce vypocitej(): 1.00111 sekund
.
V budúcej lekcii, Dekorátory druhýkrát - Parametrické a triedne dekorátory , budeme v téme dekorátorov pokračovať.