Dekorátory v jazyce Python představují mocný nástroj. Umožňují modifikovat chování funkcí tak, že je obalíme do jiných funkcí. Tím docílíme čistšího kódu a znovupoužitelnosti. Tento článek slouží jako průvodce nejen k používání dekorátorů, ale i k jejich tvorbě.
Požadované znalosti
Téma dekorátorů vyžaduje jistou úroveň znalostí. Níže jsou uvedeny koncepty, které byste měli ovládat, abyste plně porozuměli tomuto tutoriálu. Připojil jsem také zdroje pro případné osvěžení znalostí.
Základy Pythonu
Tato problematika se řadí spíše mezi středně pokročilá až pokročilá témata. Proto byste měli mít pevné základy v Pythonu, včetně datových typů, funkcí, objektů a tříd.
Měli byste také chápat objektově orientované principy, jako jsou gettery, settery a konstruktory. Pokud Python neznáte, zde jsou zdroje, které vám pomohou začít.
Funkce jako objekty první třídy
Kromě základů Pythonu byste měli znát i tento pokročilejší koncept. Funkce (a v podstatě vše v Pythonu) jsou objekty, stejně jako čísla (int) nebo textové řetězce (string). Protože jsou to objekty, můžete s nimi provádět různé akce, především:
- Předávat funkci jako argument jiné funkci, stejně jako předáváte řetězec nebo číslo.
- Vrátit funkci z jiné funkce, podobně jako vracíte jiné hodnoty, například řetězec nebo číslo.
- Ukládat funkce do proměnných.
Rozdíl mezi funkčními objekty a jinými objekty spočívá v tom, že funkční objekty mají magickou metodu __call__().
Pokud máte potřebné znalosti, můžeme přejít k hlavnímu tématu.
Co je to dekorátor v Pythonu?
Dekorátor v Pythonu je jednoduše funkce, která přijímá jinou funkci jako argument a vrací její upravenou verzi. Jinými slovy, funkce „foo“ je dekorátor, pokud přebírá funkci „bar“ jako vstup a vrací novou funkci „baz“.
Funkce „baz“ je modifikací funkce „bar“, protože v těle funkce „baz“ je volání funkce „bar“. „baz“ ale může dělat věci před i po zavolání „bar“. Následuje ilustrativní kód:
# Foo je dekorátor, přijímá funkci bar jako argument def foo(bar): # Zde vytvoříme baz, upravenou verzi funkce bar # baz volá bar, ale může před a po volání bar provádět cokoli def baz(): # Před voláním bar něco vypíšeme print("Něco se děje před voláním bar.") # Pak zavoláme bar bar() # Poté, co se bar spustí, vypíšeme ještě něco print("A ještě něco po zavolání bar.") # Nakonec foo vrací baz, upravenou verzi funkce bar return baz
Jak vytvořit dekorátor v Pythonu?
Pro ilustraci tvorby a použití dekorátorů v Pythonu uvedu jednoduchý příklad. Vytvoříme dekorátor „logger“, který bude zaznamenávat název funkce při každém jejím spuštění.
Začneme definováním funkce dekorátoru, která přijímá funkci jako argument. Proměnná „func“ reprezentuje funkci, kterou budeme dekorovat.
def create_logger(func): # Tělo funkce bude zde
Uvnitř dekorátoru vytvoříme upravenou verzi funkce, která zapíše jméno funkce „func“ před jejím spuštěním.
# Uvnitř create_logger def modified_func(): print("Volám funkci: ", func.__name__) func()
Funkce „create_logger“ vrací upravenou funkci. Celý kód dekorátoru vypadá takto:
def create_logger(func): def modified_func(): print("Volám funkci: ", func.__name__) func() return modified_func
Tímto jsme dokončili vytvoření dekorátoru. Funkce „create_logger“ je jednoduchý příklad. Přijímá funkci „func“ a vrací jinou funkci „modified_func“. Ta nejdříve zaznamená jméno funkce „func“ a poté ji spustí.
Jak používat dekorátory v Pythonu
Pro použití dekorátoru použijeme syntaxi s @, takto:
@create_logger def say_hello(): print("Ahoj světe!")
Nyní, když zavoláme funkci „say_hello()“, na výstupu se zobrazí:
Volám funkci: say_hello "Ahoj světe!"
Ale co přesně dělá „@create_logger“? Použije dekorátor na funkci „say_hello“. Kód níže dosáhne stejného výsledku jako vložení „@create_logger“ před funkci „say_hello“:
def say_hello(): print("Ahoj světe!") say_hello = create_logger(say_hello)
Dekorátory můžeme používat buď explicitním voláním dekorátoru s funkcí jako argumentem, nebo pomocí syntaxe s @. Druhá varianta je přehlednější.
Zatím jsme probrali základy vytváření dekorátorů v Pythonu.
Trochu složitější příklady
Předchozí příklad byl jednoduchý. Existují složitější situace, například když funkce, kterou dekorujeme, přijímá argumenty. Nebo když chceme dekorovat celou třídu. Tyto případy si zde probereme.
Když funkce přijímá argumenty
Pokud funkce, kterou dekorujete, přijímá argumenty, upravená funkce by měla argumenty také přijmout a předat je při volání původní funkce. Zjednodušeně řečeno: „foo“ je dekorační funkce, „bar“ je dekorovaná funkce a „baz“ je dekorovaná verze funkce „bar“. „bar“ přijme argumenty, a „baz“ je předá při volání funkce „bar“. Příklad kódu pro ujasnění konceptu:
def foo(bar): def baz(*args, **kwargs): # Zde můžeme něco dělat ___ # Poté zavoláme bar s argumenty args a kwargs bar(*args, **kwargs) # Zde můžeme zase něco dělat ___ return baz
„*args“ a „**kwargs“ reprezentují poziční argumenty a klíčové argumenty.
Funkce „baz“ má přístup k argumentům, takže můžeme například provést validaci argumentů před voláním funkce „bar“.
Například, pokud bychom chtěli mít dekorační funkci „secure_string“, která zajistí, že argument předaný funkci, kterou dekoruje, bude textový řetězec, implementovali bychom ji takto:
def ensure_string(func): def decorated_func(text): if type(text) is not str: raise TypeError('Argument funkce ' + func.__name__ + ' musí být řetězec.') else: func(text) return decorated_func
Funkci „say_hello“ bychom mohli dekorovat následovně:
@ensure_string def say_hello(name): print('Ahoj', name)
Kód můžeme otestovat takto:
say_hello('Jana') # Mělo by fungovat bez problémů say_hello(3) # Mělo by vyvolat výjimku
Výstup by měl být následující:
Ahoj Jana Traceback (most recent call last): File "/home/anesu/Documents/python-tutorial/./decorators.py", line 20, in <module> say hello(3) # should throw an exception File "/home/anesu/Documents/python-tu$ ./decorators.pytorial/./decorators.py", line 7, in decorated_func raise TypeError('argument to + func._name_ + must be a string.') TypeError: Argument funkce say_hello musí být řetězec. $0
Jak se očekávalo, „Ahoj Jana“ se vypíše, protože „Jana“ je řetězec. Pokus o „Ahoj 3“ vyvolal výjimku, protože 3 není řetězec. Dekorační funkci „secure_string“ můžeme použít k ověření argumentů jakékoli funkce, která vyžaduje textový řetězec.
Dekorování třídy
Kromě funkcí můžeme dekorovat i třídy. Když přidáte dekorátor ke třídě, nahradí dekorovaná metoda metodu konstruktoru třídy (__init__).
Opět si představme „foo“ jako náš dekorátor a „Bar“ jako třídu, kterou dekorujeme, „foo“ tedy dekoruje „Bar.__init__“. To se hodí, pokud chceme něco udělat před vytvořením instancí objektů typu „Bar“.
Následující kód
def foo(func): def new_func(*args, **kwargs): print('Provádím nějaké věci před vytvořením instance') func(*args, **kwargs) return new_func @foo class Bar: def __init__(self): print("V konstruktoru")
je ekvivalentní kódu:
def foo(func): def new_func(*args, **kwargs): print('Provádím nějaké věci před vytvořením instance') func(*args, **kwargs) return new_func class Bar: def __init__(self): print("V konstruktoru") Bar.__init__ = foo(Bar.__init__)
Vytvoření instance objektu třídy „Bar“, ať už kteroukoli metodou, bude mít za výstup:
Provádím nějaké věci před vytvořením instance V konstruktoru
Příklady dekorátorů v Pythonu
Kromě definování vlastních dekorátorů existují některé, které jsou již zabudovány v Pythonu. Zde je několik běžných dekorátorů, se kterými se můžete setkat:
@staticmethod
Dekorátor „@staticmethod“ označuje, že metoda, kterou dekoruje, je statická. Statické metody lze spustit bez nutnosti vytvářet instanci třídy. V následujícím příkladu kódu vytvoříme třídu „Pes“ se statickou metodou „štěkej“.
class Pes: @staticmethod def stekej(): print('Haf, haf!')
Metodu „štěkej“ můžeme zavolat takto:
Pes.stekej()
Výstupem bude:
Haf, haf!
Jak jsem již zmínil, dekorátory lze použít dvěma způsoby. Syntax s @ je stručnější, ale lze volat i funkci dekorátoru s funkcí, kterou chceme dekorovat, jako argument. Následující kód dosahuje stejného výsledku:
class Pes: def stekej(): print('Haf, haf!') Pes.stekej = staticmethod(Pes.stekej)
Metodu „štěkej“ můžeme používat stejným způsobem:
Pes.stekej()
A dostaneme stejný výstup:
Haf, haf!
Jak vidíte, první metoda je přehlednější a je zřejmé, že jde o statickou metodu ještě předtím, než začnete číst kód. V následujících příkladech proto použiji první metodu, ale nezapomeňte, že druhá metoda je alternativa.
@classmethod
Tento dekorátor se používá k označení, že metoda, kterou dekoruje, je metoda třídy. Metody třídy jsou podobné statickým metodám v tom, že obě nevyžadují vytvoření instance třídy před jejich voláním.
Hlavní rozdíl je v tom, že metody třídy mají přístup k atributům třídy, zatímco statické metody ne. Python automaticky předává třídu jako první argument metodě třídy. Můžeme použít dekorátor „@classmethod“ pro vytvoření metody třídy.
class Pes: @classmethod def kdo_jsem(cls): print("Jsem " + cls.__name__ + "!")
Kód spustíme zavoláním metody bez vytváření instance třídy:
Pes.kdo_jsem()
Výstup je:
Jsem Pes!
@property
Dekorátor „@property“ se používá k označení metody jako getter vlastnosti. Vraťme se k našemu příkladu se psem, vytvoříme metodu, která získá jméno psa.
class Pes: # Vytvoříme konstruktor, který přijme jméno psa def __init__(self, jmeno): # Vytvoříme privátní vlastnost jmeno # Dvojité podtržítka dělají atribut privátním self.__jmeno = jmeno @property def jmeno(self): return self.__jmeno
Nyní můžeme přistupovat ke jménu psa jako k normální vlastnosti.
# Vytvoříme instanci třídy haf = Pes('hafík') # Přistupujeme k vlastnosti jmeno print("Jméno psa je:", haf.jmeno)
Výstup bude:
Jméno psa je: hafík
@property.setter
Dekorátor „@property.setter“ se používá k vytvoření metody setter pro naše vlastnosti. Pro použití dekorátoru „@property.setter“ nahradíte „property“ jménem vlastnosti, pro kterou vytváříte setter. Například, pokud vytváříte setter pro vlastnost „foo“, váš dekorátor bude „@foo.setter“. Zde je příklad se psem pro ilustraci:
class Pes: # Vytvoříme konstruktor, který přijme jméno psa def __init__(self, jmeno): # Vytvoříme privátní vlastnost jmeno # Dvojité podtržítka dělají atribut privátním self.__jmeno = jmeno @property def jmeno(self): return self.__jmeno # Vytvoříme setter pro naši vlastnost jmeno @jmeno.setter def jmeno(self, nove_jmeno): self.__jmeno = nove_jmeno
Setter můžeme otestovat pomocí:
# Vytvoříme nového psa haf = Pes('hafík') # Změníme jméno psa haf.jmeno="rex" # Vypíšeme jméno psa print("Nové jméno psa je:", haf.jmeno)
Výstup bude:
Nové jméno psa je: rex
Význam dekorátorů v Pythonu
Nyní, když jsme si vysvětlili, co jsou dekorátory, a viděli jsme několik příkladů, můžeme diskutovat o tom, proč jsou dekorátory v Pythonu tak důležité. Důležité jsou z několika důvodů, například:
- Umožňují opětovné použití kódu: V příkladu s logováním jsme mohli použít „@create_logger“ na jakoukoli funkci. Můžeme tak přidat funkci logování ke všem funkcím, aniž bychom museli kód psát pro každou funkci zvlášť.
- Umožňují psát modulární kód: Opět v příkladu s logováním oddělíte funkci logování od hlavní funkce „say_hello“.
- Vylepšují frameworky a knihovny: Dekorátory jsou široce používány v rámci knihoven Pythonu pro poskytování dalších funkcí. Například ve webových frameworkech, jako je Flask nebo Django, se dekorátory používají pro definování tras, zpracování autentizace nebo pro použití middleware na určité pohledy.
Závěrečná slova
Dekorátory jsou neuvěřitelně užitečné. Můžete je použít k rozšíření funkcí bez změny jejich primární funkčnosti. Je to užitečné pro měření výkonu funkcí, logování každého volání funkce, ověřování argumentů nebo autorizaci před spuštěním funkce. Jakmile dekorátory pochopíte, budete schopni psát čistší kód.
Dále si můžete přečíst naše články o n-ticích a o používání cURL v Pythonu.