Dekorátor převezme funkci, přidá některé funkce a vrátí ji. V tomto tutoriálu se dozvíte, jak můžete vytvořit dekorátor a proč byste jej měli používat.
Dekoratéři v Pythonu
Python má zajímavou funkci zvanou dekoratéři, která přidává funkce do existujícího kódu.
Toto se také nazývá metaprogramování, protože část programu se pokouší upravit jinou část programu v době kompilace.
Předpoklady pro učení dekoratérů
Abychom porozuměli dekoratérům, musíme nejprve znát několik základních věcí v Pythonu.
Musíme být spokojeni s tím, že všechno v Pythonu (Ano! Dokonce i třídy) jsou objekty. Názvy, které definujeme, jsou jednoduše identifikátory vázané na tyto objekty. Funkce nejsou výjimkou, jsou to také objekty (s atributy). Ke stejnému funkčnímu objektu lze vázat různé různé názvy.
Zde je příklad.
def first(msg): print(msg) first("Hello") second = first second("Hello")
Výstup
Ahoj ahoj
Když spustíte kód, obě funkce first
a second
poskytují stejný výstup. Zde názvy first
a second
odkazují na stejný funkční objekt.
Nyní věci začínají být divnější.
Funkce lze předat jako argumenty jiné funkci.
Pokud jste použili funkce, jako je map
, filter
a reduce
v Pythonu, pak již víte o tom.
Takovým funkcím, které jako argumenty berou jiné funkce, se také říká funkce vyššího řádu . Zde je příklad takové funkce.
def inc(x): return x + 1 def dec(x): return x - 1 def operate(func, x): result = func(x) return result
Funkci vyvoláme následujícím způsobem.
>>> operate(inc,3) 4 >>> operate(dec,3) 2
Funkce navíc může vrátit jinou funkci.
def is_called(): def is_returned(): print("Hello") return is_returned new = is_called() # Outputs "Hello" new()
Výstup
Ahoj
Zde is_returned()
je vnořená funkce, která je definována a vrácena pokaždé, když zavoláme is_called()
.
Nakonec musíme vědět o Closures v Pythonu.
Vracíme se k dekoratérům
Funkce a metody se nazývají volatelné, jak je lze volat.
Ve skutečnosti je každý objekt, který implementuje speciální __call__()
metodu, nazýván volatelný. V nejzákladnějším smyslu je tedy dekorátor volatelný, který vrací volatelný.
V zásadě dekoratér převezme funkci, přidá některé funkce a vrátí ji.
def make_pretty(func): def inner(): print("I got decorated") func() return inner def ordinary(): print("I am ordinary")
Když spustíte následující kódy ve skořápce,
>>> ordinary() I am ordinary >>> # let's decorate this ordinary function >>> pretty = make_pretty(ordinary) >>> pretty() I got decorated I am ordinary
Ve výše uvedeném příkladu make_pretty()
je dekoratér. V kroku přiřazení:
pretty = make_pretty(ordinary)
Funkce ordinary()
byla vyzdobena a vrácená funkce dostala jméno pretty
.
Vidíme, že funkce dekorátoru přidala k původní funkci některé nové funkce. Je to podobné jako při balení dárku. Dekorátor funguje jako obal. Povaha zdobeného předmětu (skutečný dar uvnitř) se nemění. Ale teď to vypadá hezky (protože to bylo zdobené).
Obecně funkci zdobíme a přiřadíme ji jako,
ordinary = make_pretty(ordinary).
Toto je běžný konstrukt az tohoto důvodu má Python syntaxi, která to zjednoduší.
Můžeme použít @
symbol spolu s názvem funkce dekorátoru a umístit jej nad definici zdobené funkce. Například,
@make_pretty def ordinary(): print("I am ordinary")
je ekvivalentní k
def ordinary(): print("I am ordinary") ordinary = make_pretty(ordinary)
Jedná se pouze o syntaktický cukr pro implementaci dekoratérů.
Zdobení funkcí pomocí parametrů
Výše uvedený dekorátor byl jednoduchý a pracoval pouze s funkcemi, které neměly žádné parametry. Co kdybychom měli funkce, které přijímaly parametry jako:
def divide(a, b): return a/b
Tato funkce má dva parametry, a a b. Víme, že udělá chybu, když předáme b jako 0.
>>> divide(2,5) 0.4 >>> divide(2,0) Traceback (most recent call last):… ZeroDivisionError: division by zero
Nyní vytvořme dekoratér, který zkontroluje tento případ, který způ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)
Tato nová implementace se vrátí, None
pokud nastane chybový stav.
>>> 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 způsobem můžeme zdobit funkce, které berou parametry.
Nadšený pozorovatel si všimne, že parametry vnořené inner()
funkce uvnitř dekorátoru jsou stejné jako parametry funkcí, které zdobí. Když to vezmeme v úvahu, nyní můžeme vytvořit obecné dekoratéry, které pracují s libovolným počtem parametrů.
In Python, this magic is done as function(*args, **kwargs)
. In this way, args
will be the tuple of positional arguments and kwargs
will be the dictionary of keyword arguments. An example of such a decorator will be:
def works_for_all(func): def inner(*args, **kwargs): print("I can decorate any function") return func(*args, **kwargs) return inner
Chaining Decorators in Python
Multiple decorators can be chained in Python.
This is to say, a function can be decorated multiple times with different (or same) decorators. We simply place the decorators above the desired function.
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")
Output
****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Hello %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ******************************
The above syntax of,
@star @percent def printer(msg): print(msg)
is equivalent to
def printer(msg): print(msg) printer = star(percent(printer))
The order in which we chain decorators matter. If we had reversed the order as,
@percent @star def printer(msg): print(msg)
The output would be:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ****************************** Hello ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%