Dekorátory

Co jsou dekorátory

Tato kapitola je o dalším pythonovském syntaktickém cukru, dekorátorech. Dekorátory nejsou zrovna tím hlavním a nejdůležitejším rysem Pythonu, ale můžete se s nimi setkat na mnoha místech v dokumentaci, tak je asi dobré vědět o co jde.

Dekorátorem se nazývá nějaká funkce nebo třída, která rozšiřuje nebo mění funkčnost jiné třídy nebo funkce. Používají se tam, kde nechcete funkčnost původní funkce/třídy měnit, ale chcete, aby se funkce/třída daného jména chovala trochu jinak.

Jak víte, funkce i třídy jsou v Pythonu objekty, takže je můžete přiřazovat do proměnných, vytvářet uvnitř jiných funkcí atp.

Uvažujte následující příklad. Řekněme, že máte program, který vypisuje sposutu textu pomocí funkce print. Vy ale chcete mít možnost všechen výpis nějak vypnout. Místo funkce print budete chctít použít tuto funkci:

def xprint(*args, **kwargs):
    try:
      if(debug == True):
         print(*args, **kwargs)
    except NameError: # debug neni definovan
         pass
Tento příklad funguje jen v Pythonu 3.0 a vyšším kvůli syntaxi print(*args, **kwargs), nicméně dekorátory jako takové můžete psát v jakékoliv verzi Pythonu. Speciální syntax pro dekorátory (popisovaná níže) funguje od Pythonu 2.6.

Funkce vypíše text jen když je definována globální proměnná debug a má hodnotu true.

>>> xprint("Hello World")
>>> debug = True
>>> xprint("Hello World")
Hello World

Bezva. Teď můžete všude v kódu nahradit print za xprint. To je ale zbytečně moc práce. Dekorace je o tom, že přímo změníte funkčnost print. Co takhle využít toho, že funkce je jen objekt, jehož jménu můžeme přiřadit cokoliv jiného? Třeba takto:

>>> print = xprint
>>> print("Hello World")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in xprint
  File "<stdin>", line 4, in xprint
  File "<stdin>", line 4, in xprint
...
RuntimeError: maximum recursion depth exceeded in comparison

Tak to nevyšlo. Funkce xprint volá print, což se stalo jiné jméno pro xprint, takže dochází k rekurzivnímu volání xprint a kód funkce print je ztracen.

Chtělo by si to print nejdřív uložit do jiného jména a to použít uvnitř xprint. Zkusím to udělat tak, abych nemusel nové jméno definovat v globálním jmenném prostoru. Zabalím proto funkci xprint i s novým jménem do jiné funkce. Tato funkce funkci xprint vrátí jako výsledek svého volání.

def dekorator_print():
    fce = print
    def xprint(*args, **kwargs):
        try:
            if(debug == True):
                 fce(*args, **kwargs)
        except NameError: # debug neni definovan
            pass
    return xprint
>>> print("Hello World")
Hello World
>>> print = dekorator_print()
>>> print("Hello World")
>>> debug = True
>>> print("Hello World")
Hello World

To je mnohem lepší. Co to napsat ale trochu víc obecně, abychom mohli takto „odekorovat“ více funkcí? Můžeme chtít zapínat/vypínat při debugování nejen print

Stačí vcelku banální úprava (dekorátor jsem přejmenoval, protože už není míněn jen pro print):

def dekorator_debug(fce):
    def xprint(*args, **kwargs):
        try:
            if(debug == True):
                 fce(*args, **kwargs)
        except NameError: # debug neni definovan
            pass
    return xprint
>>> print = dekorator_debug(print)
>>> print("Hello World")
>>> debug = True
>>> print("Hello World")
Hello World

Syntaktický cukr

Teď už víte čemu se říká dekorátor. Pokud budte chtít nějaký použít na vlastní funkci (nebo metodu, třídu), můžete to udělat podobně jako v příkladu výše, nebo pomocí syntaxe se zavynáčem, kterou vám k tomu Python poskytuje.

Následující dva kódy jsou ekvivalentní:

def debuginfo(text):
     print("info: "+text)
 
debuginfo = dekorator_debug(debuginfo)

def debugwarning(text):
     print("debugwarning: "+text)
 
debugwarning = dekorator_debug(debugwarning)
@dekorator_debug
def debuginfo(text):
     print("info: "+text)
 
@dekorator_debug
def debugwarning(text):
     print("debugwarning: "+text)

Dekorátory Pythonu

Python definuje celou řadu dekorátorů. Většinou na ně narazíte při pročítání dokumentace.

Třeba takový dekorátor @classmethod se používá pro vytváření třídních metod tříd (metody, které jako první argument nedostanou instanci třídy, ale třídu).

class Test:
    def instance_method(self):
        print(self)
    @classmethod
    def class_method(cls):
       print(cls)
>>> a = Test()
>>> a.instance_method()
<__main__.Test object at 0x7fd5e2088fd0>
>>> a.class_method()
<class '__main__.Test'>

Pokud vás tato kapitola zaujala, doporučuji přečíst A guide to Python's function decorators.

Komentář Hlášení chyby
Created: 1.9.2015
Last updated: 1.9.2015
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..