Dekoratér prevezme funkciu, pridá určitú funkcionalitu a vráti ju. V tomto návode sa dozviete, ako môžete vytvoriť dekoratér a prečo by ste ho mali používať.
Dekoratéri v Pythone
Python má zaujímavú funkciu zvanú dekoratéri, ktorá pridáva funkčnosť existujúcemu kódu.
Toto sa tiež nazýva metaprogramovanie, pretože časť programu sa pokúša upraviť inú časť programu v čase kompilácie.
Predpoklady na učenie dekoratérov
Aby sme porozumeli dekoratérom, musíme v Pythone najskôr vedieť niekoľko základných vecí.
Musíme byť spokojní s tým, že všetko v Pythone (Áno! Dokonca aj triedy), sú objekty. Názvy, ktoré definujeme, sú jednoducho identifikátory viazané na tieto objekty. Funkcie nie sú výnimkou, sú to tiež objekty (s atribútmi). Na ten istý funkčný objekt je možné naviazať rôzne rôzne názvy.
Tu je príklad.
def first(msg): print(msg) first("Hello") second = first second("Hello")
Výkon
Ahoj ahoj
Pri spustení kódu, obe funkcie first
a second
dávajú rovnaký výstup. Tu názvy first
a second
odkazujú na rovnaký funkčný objekt.
Teraz to začne byť čudnejšie.
Funkcie je možné odovzdať ako argumenty inej funkcii.
Ak ste použili funkcie, ako je map
, filter
a reduce
v Pythone, potom už viete o tom.
Také funkcie, ktoré ako argumenty berú iné funkcie, sa nazývajú aj funkcie vyššieho rádu . Tu je príklad takejto funkcie.
def inc(x): return x + 1 def dec(x): return x - 1 def operate(func, x): result = func(x) return result
Funkciu vyvoláme nasledovne.
>>> operate(inc,3) 4 >>> operate(dec,3) 2
Ďalej môže funkcia vrátiť inú funkciu.
def is_called(): def is_returned(): print("Hello") return is_returned new = is_called() # Outputs "Hello" new()
Výkon
Ahoj
Tu is_returned()
je vnorená funkcia, ktorá je definovaná a vrátená zakaždým, keď zavoláme is_called()
.
Nakoniec musíme vedieť o uzáveroch v Pythone.
Vraciame sa k dekoratérom
Funkcie a metódy sú volány callable pretože možno nazvať.
V skutočnosti je každý objekt, ktorý implementuje špeciálnu __call__()
metódu, nazývaný volaný. Takže v najzákladnejšom zmysle je dekoratér volaný, ktorý vracia volaný.
V zásade dekoratér prevezme funkciu, pridá určitú funkcionalitu a vráti ju.
def make_pretty(func): def inner(): print("I got decorated") func() return inner def ordinary(): print("I am ordinary")
Keď spustíte nasledujúce kódy v prostredí shell,
>>> ordinary() I am ordinary >>> # let's decorate this ordinary function >>> pretty = make_pretty(ordinary) >>> pretty() I got decorated I am ordinary
V príklade uvedenom vyššie make_pretty()
je dekoratér. V kroku priradenia:
pretty = make_pretty(ordinary)
Funkcia bola ordinary()
ozdobená a vrátená funkcia dostala meno pretty
.
Vidíme, že funkcia dekorátora pridala k pôvodnej funkcii nejaké nové funkcie. Je to podobné ako s balením darčeka. Dekorátor funguje ako obal. Povaha zdobeného predmetu (skutočný darček vo vnútri) sa nemení. Ale teraz to vyzerá pekne (keďže to bolo vyzdobené).
Spravidla zdobíme funkciu a znova ju priradíme ako,
ordinary = make_pretty(ordinary).
Toto je bežný konštrukt a z tohto dôvodu má Python syntax, ktorá to zjednodušuje.
Môžeme použiť @
symbol spolu s názvom funkcie dekorátora a umiestniť ho nad definíciu funkcie, ktorá sa má zdobiť. Napríklad,
@make_pretty def ordinary(): print("I am ordinary")
je ekvivalentné k
def ordinary(): print("I am ordinary") ordinary = make_pretty(ordinary)
Toto je iba syntaktický cukor na implementáciu dekoratérov.
Zdobenie funkcií pomocou parametrov
Vyššie uvedený dekoratér bol jednoduchý a pracoval iba s funkciami, ktoré nemali žiadne parametre. Čo keby sme mali funkcie, ktoré by brali parametre ako:
def divide(a, b): return a/b
Táto funkcia má dva parametre, a a b. Vieme, že ak zadáme b ako 0, bude to mať chybu.
>>> divide(2,5) 0.4 >>> divide(2,0) Traceback (most recent call last):… ZeroDivisionError: division by zero
Teraz urobme dekoratér, ktorý skontroluje tento prípad, ktorý spôsobí chybu.
def smart_divide(func): def inner(a, b): print("I am going to divide", a, "and", b) if b == 0: print("Whoops! cannot divide") return return func(a, b) return inner @smart_divide def divide(a, b): print(a/b)
Táto nová implementácia sa vráti, None
ak dôjde k chybovému stavu.
>>> divide(2,5) I am going to divide 2 and 5 0.4 >>> divide(2,0) I am going to divide 2 and 0 Whoops! cannot divide
Týmto spôsobom môžeme zdobiť funkcie, ktoré berú parametre.
Vášnivý pozorovateľ si všimne, že parametre vnorenej inner()
funkcie vo vnútri dekorátora sú rovnaké ako parametre funkcií, ktoré zdobí. Ak to vezmeme do úvahy, teraz môžeme vyrobiť všeobecné dekoratéry, ktoré pracujú s ľubovoľným počtom parametrov.
V Pythone sa táto mágia robí ako function(*args, **kwargs)
. Týmto spôsobom args
bude n-ticou pozičných argumentov a kwargs
bude slovníkom argumentov kľúčových slov. Príkladom takéhoto dekoratéra bude:
def works_for_all(func): def inner(*args, **kwargs): print("I can decorate any function") return func(*args, **kwargs) return inner
Reťazenie dekoratérov v Pythone
V Pythone je možné reťaziť viac dekoratérov.
To znamená, že funkcia môže byť zdobená viackrát rôznymi (alebo rovnakými) dekorátormi. Dekorátory jednoducho umiestnime nad požadovanú funkciu.
def star(func): def inner(*args, **kwargs): print("*" * 30) func(*args, **kwargs) print("*" * 30) return inner def percent(func): def inner(*args, **kwargs): print("%" * 30) func(*args, **kwargs) print("%" * 30) return inner @star @percent def printer(msg): print(msg) printer("Hello")
Výkon
******************************** %%%%%%%%%%%%%%%%%%%%% %%%%%%%%%% Ahoj %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ********* **********************
Vyššie uvedená syntax,
@star @percent def printer(msg): print(msg)
je ekvivalentné k
def printer(msg): print(msg) printer = star(percent(printer))
Na poradí, v akom spájame dekoratérov, záleží. Keby sme obrátili poradie ako,
@percent @star def printer(msg): print(msg)
Výstup by bol:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ********************* ********** Ahoj ****************************** %%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%