<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xml" href="/cjs/screen.xsl" media="screen"?>
<lecture>

<meta>
  <maintitle>Python</maintitle>
  <author>Jiří Znamenáček</author>
  <title>Dekorátory</title>
  <date>2011-04-28</date>
  <link><!--a href="http://vyuka.ookami.cz" rel="external">http://vyuka.ookami.cz</a--></link>
  <style>
    div.color { color: black; background-color: cornsilk; }
  </style>
</meta>
<!--
  „“–
  ↵ aneb &#x21B5; aneb \r aneb CR aneb CarriageReturn
-->


<slide title="Úvod">

  <p>
    <strong>Dekorátory</strong> obecně jsou funkce, které berou jako parametr funkci (a případně nějaké další doplňující parametry) a vrací tuto funkci zadaným způsobem upravenou, „odekorovanou“.
  </p>
  <note>
    Čistě teoreticky může dekorátor funkci redukovat na libovolnou návratovou hodnotu, ale to by bylo velmi specifické použití.
  </note>

  <p>
    Většinou se pro jejich zápis používá konstrukce <code>@DEKORÁTOR</code>. Jinými slovy konstrukce..
  </p>
  <example lang="python">
@dekorátor
def moje_funkce():
    BLOK
  </example>
  <p>
    ..je vlastně přehlednější náhrada za..
  </p>
  <example lang="python">
def moje_funkce():
    BLOK

moje_funkce = dekorátor(moje_funkce)
  </example>

</slide>
<slide title="Poznámka">

    <p>
        Funkce dekorátoru se v praxi snad nejčastěji projevuje tak, že návratové hodnoty z dekorované funkce jsou nějakým způsobem omezeny či změněny, proto by člověk mohl být v pokušení napsat dekorovací funkci jako operátor vracející tyto nějak upravené hodnoty. Jenže ve skutečnosti dekorátor dekorovanou funkci vlastně zcela nahrazuje, a proto musí být pochopitelně sám volatelným objektem! Jinak řečeno:
    </p>
    <blockquote>
        Vstupem dekorátoru je objekt dekorované funkce a výstupem objekt nějaké jiné funkce.
    </blockquote>
    <p>
        Kvůli tomu je konstrukce dekorátorů kapku složitější, než by člověk na první pohled mohl očekávat.
    </p>

</slide>
<slide title="Konstrukce dekorátoru I">

    <p>
        Zavedeme-li dekorátor..
    </p>
    <example lang="python">
def dekorátor(funkce):
    def _dekorátor():
        print('START')
        return funkce()
    return _dekorátor
    </example>
    <p>
        ..pak volání následujícím způsobem odekorované funkce..
    </p>
    <example lang="python">
@dekorátor
def moje_funkce():
    print('moje funkce')
    </example>
    <note>
        Tedy rozepsaně:
        <example lang="python">moje_funkce = dekorátor(moje_funkce)</example>
    </note>
    <p>
        ..vrátí:
    </p>
    <example lang="python">
>>> moje_funkce()
START
moje funkce
    </example>
    <p>
        Celé toto funguje právě proto, že funkce uvnitř funkcí si s sebou nesou celý aktuální (dynamický) kontext, jak jsme viděli už dříve na případě <a href="advanced.xml?slajd=4">uzávěrů</a> (<em>closures</em>).
    </p>

</slide>
<slide title="Poznámka">
    
    <p>
        Odekorovaná funkce se sice jmenuje stejně, ale pochopitelně je to už úplně jiná funkce:
    </p>
    <example lang="python">
        >>> def dekorátor(funkce):
        ...     def _dekorátor():
        ...         print('START')
        ...         return funkce()
        ...     return _dekorátor

        >>> def moje_funkce():
        ...     print('moje funkce')
        >>> moje_funkce
        &lt;function moje_funkce at 0x000001E6B32DB0A0>

        >>> @dekorátor
        ... def moje_funkce():
        ...     print('moje funkce')
        >>> moje_funkce
        &lt;function dekorátor.&lt;locals>._dekorátor at 0x000001E6B32DB1C0>
    </example>
    
</slide>
<slide title="Konstrukce dekorátoru II">

    <p>
        Zpracování návratové hodnoty funkce dekorátorem nebo prostě nějaká další dodatečná činnost ještě po zavolání funkce vyžaduje následující úpravu:
    </p>
    <example lang="python">
        def _dekorátor():
            print('START')
            ret = funkce()
            print('END')
            return ret
    </example>
    <p>
        Dekorovanou funkci je tedy třeba nejdříve vykonat (zavolat), návratovou hodnotu – ať už je jí cokoli – si uschovat pro pozdější použití, vykonat zbývající práci dekorátoru (což tedy může být a často právě bývá úprava návratové hodnoty) a teprve poté vrátit (případně upravenou) spočítanou hodnotu původní funkce.
    </p>

</slide>
<slide title="Příklad">

    <p>
        Následující dekorátor při volání funkce vypíše její jméno a spočítá dobu jejího běhu. Všimněte si však především, že <strong>jeden dekorátor použijeme na vícero různých funkcí, aniž bychom změnili jejich výchozí chování</strong>, a přitom se dozvíme něco navíc:
    </p>
    <example layout="vertical">
        <program src="_files/decorators.2.py" lang="python"/>
        <out src="_files/decorators.2.out" lang="text"/>
    </example>

</slide>
<slide title="PS: Atributy funkcí">

    <p>
        Jak jsme viděli u atributu <code>__name__</code> na předchozím slajdu, funkce je interně také „jenom“ nějaký objekt se složitější strukturou. A jako takovému jí můžeme třeba i nastavovat vlastní atributy:
    </p>
    <example lang="python">
        def dekorátor(funkce):
            def _dekorátor():
                return funkce()
            _dekorátor.atribut = "Ahoj!"
            return _dekorátor

        @dekorátor
        def moje_funkce():
            print( 'moje funkce' )
        
        >>> moje_funkce()
        moje funkce
        >>> moje_funkce.atribut
        'Ahoj!'
        >>> moje_funkce.__dict__
        {'atribut': 'Ahoj!'}
    </example>
    <note>
        A to pochopitelně nejen v rámci dekorátorů jako zde.
    </note>

</slide>
<slide title="Předávání parametrů funkci">

  <p>
    Bude-li dekorovaná funkce očekávat nějaké vstupní parametry, syntaxe se nepatrně změní o vnitřní parametr:
  </p>
  <example lang="python">
>>> def dekorátor(funkce):
...     def _dekorátor(x):
...         print('START')
...         ret = funkce(x)
...         return ret
...     return _dekorátor
... 
... @dekorátor
... def moje_funkce(s):
...     print( 'Ahoj, ', s, '!', sep='' )

>>> moje_funkce('Láďo')
START
Ahoj, Láďo!
  </example>
  <note>
    Zatím se nic nezměnilo, vracíme stále stejnou funkci, která prostě jen navíc očekává vstupní parametr:
    <example lang="python">moje_funkce = dekorátor(moje_funkce)</example>
  </note>

</slide>
<slide title="Příklad">

  <p>
    Ukažme si rozumnější příklad – dekorátor, který při volání funkce vypíše všechny její parametry a navíc jeden (<em>debug</em>) nastaví, čímž i změní její chování:
  </p>
  <example lang="python">
>>> def decorator(target):
... 
...   def wrapper(*args, **kwargs):
...     # Edit the keyword arguments
...     #   -- here, enable debug mode no matter what
...     kwargs.update({'debug': True})
...     print(
...       'Calling function "%s" with arguments %s and keyword arguments %s'
...       % (target.__name__, args, kwargs)
...     )
...     return target(*args, **kwargs)
... 
...   wrapper.attribute = 1
...   return wrapper

>>> @decorator
... def target(a, b, debug=False):
...   if debug: print('[Debug] I am the target function')
...   return a + b

# volání funkce je nyní odekorované, tzn. 'debug' je nastaven na True..
>>> target(1,2)
Calling function "target" with arguments (1, 2)
                and keyword arguments {'debug': True}
[Debug] I am the target function
3

# ..a funkci přibyl nový atribut 'attribute'
>>> target.attribute
1
  </example>
  <notes>
      <note>
        Podle <a href="http://www.siafoo.net/article/68" class="external">http://www.siafoo.net/article/68</a> .
      </note>
      <note>
        (Zalámáno pro lepší čitelnost.)
      </note>
  </notes>

</slide>
<slide title="Předávání parametrů dekorátoru I">

  <p>
    Začněme příkladem. Při zavedení následujícího dekorátoru..
  </p>
  <example lang="python">
def omez_hodnoty(maximum):        # funkce přebírající parametry dekorátoru
    def _omez_hodnoty(funkce):    # funkce přebírající dekorovanou funkci
        def __omez_hodnoty(xs):   # konečná návratová funkce
            ret = funkce( [x for x in xs if x &lt;= maximum] )
            return ret
        return __omez_hodnoty
    return _omez_hodnoty
  </example>
  <p>
    ..můžeme provést odekorování např. takto:
  </p>
  <example lang="python">
@omez_hodnoty(9)
def zpracuj_hodnoty(xs):
    return [x**2 for x in xs]
  </example>
  <note>
    Tohle už je zajímavější:
    <example lang="python">zpracuj_hodnoty = omez_hodnoty(9)(zpracuj_hodnoty)</example>
    Tedy kód <code>omez_hodnoty(9)</code> vrací vlastně funkci <code>_omez_hodnoty</code>, které jako parametr předhodíme původní funkci, to jest <code>_omez_hodnoty(zpracuj_hodnoty)</code>, čímž dostaneme odkaz na funkci <code>__omez_hodnoty</code>, která očekává vstupní parametr <code>xs</code>. A ta pak už volá úplně původní funkci s parametry omezenými podle vstupu dekorátoru.
  </note>
  <p>
    Volání funkce <em>zpracuj_hodnoty()</em> pak vrátí:
  </p>
  <example lang="python">
>>> test = zpracuj_hodnoty( [1, 3, 11, 4, 7, 14, 5] )
>>> print(test)
[1, 9, 16, 49, 25]
  </example>

</slide>
<slide title="Předávání parametrů dekorátoru II">

  <p>
    Potřebujeme-li předat parametry nikoli dekorované funkci, ale dekorátoru samotnému, konstrukce se zesložitila o další mezifunkci, která tyto parametry přebírá a posílá dál. Sice už to vypadá kapku nepřehledně, ale v podstatě by se dalo říci, že v tomto případě prostě:
  </p>
  <blockquote>
    Musíme vyrobit funkci, která z jí zadaných parametrů vyrobí příslušný dekorátor.
  </blockquote>
  <p>
    Dekorátor sám je tedy stále <em>funkce vrácená z funkce</em>, ale tentokrát je sám navíc vyroben (a tedy vrácen) další funkcí. Která prostě jenom přebírá příslušné parametry, které použije k jeho tvorbě:
  </p>
  <table>
    <tr>
      <th>původní dekorátor</th>
      <th>→</th>
      <th>funkce vyrábějící dekorátor</th>
    </tr>
    <tr>
      <td>
        <pre>

<div class="color">def _omez_hodnoty(funkce):
    def __omez_hodnoty(xs):
        ret = funkce(x for x in xs if x &lt;= mx)
        return ret
    return __omez_hodnoty</div>        </pre>
      </td>
      <td></td>
      <td>
        <pre>
def omez_hodnoty(mx):
<div class="color">    def _omez_hodnoty(funkce):
        def __omez_hodnoty(xs):
            ret = funkce(x for x in xs if x &lt;= mx)
            return ret
        return __omez_hodnoty</div>    return _omez_hodnoty </pre>
      </td>
    </tr>
  </table>
  <note>
      Hodnotu <em>mx</em> pro omezení vstupů musí dekorátor nalevo odněkud získat. A obalení další funkcí (která tento dekorátor tedy vlastně vyrobí) je právě ten kontext, odkud se tato hodnota objeví.
  </note>

</slide>
<slide title="Řetězení dekorátorů I">

    <p>
        Stejně jako funkce můžeme zanořovat do sebe, <code>@</code>dekorátory můžeme přidávat před definici funkce. Aplikují se pak v uvedeném pořadí:
    </p>
    <example layout="vertical">
        <program src="_files/decorators.1.py" lang="python"/>
        <out src="_files/decorators.1.out" lang="text"/>
    </example>
    <note>
        Dává-li to smysl, můžete samozřejmě za sebou řetězit stejný dekorátor ^_~
    </note>

</slide>
<slide title="Řetězení dekorátorů II">

    <p>
        Pro komplikovanější řetězení dekorátorů pak volání vypadá tak, jak jsme už viděli u <a href="?slajd=8">předávání parametrů dekorátoru</a>. Například (bez invence přímo podle dokumentace)..
    </p>
    <example lang="python">
        @f1(arg)
        @f2
        def func():
            pass
    </example>
    <p>
        je ekvivalentní tomutu kódu:
    </p>
    <example lang="python">
        func():
            pass
        func = f1(arg)(f2(func))
    </example>
    <p>
        Volání komplikovanějších případů je snad z této ukázky a hlavně předchozího příkladu již celkem zřejmé: Parametrem dekorátoru je vždy funkce, tudíž čeká-li sám dekorátor nějaké řídicí parametry, musí vrátit funkci, jejímž parametrem teprve bude funkce následující.
    </p>
    <note>
        V tomto příkladu tedy volání <code>f1(arg)</code> vrátí funkci, která teprve slouží jako vlastní dekorátor funkce <code>f2</code> (která je zase vlastním dekorátorem původní funkce <code>func</code>).
    </note>

</slide>
<slide title="Použití">

  <p class="enumerate">
    Python sám nabízí několik připravených dekorátorů. Asi nejviditelnější jsou dekorátory <code>@classmethod</code> a <code>@staticmethod</code> u tříd, které zajišťují, že příslušné metody třídy se nechovají jako instanční, ale jako třídní nebo statické.
  </p>
  <handout>
    Odhaduji na kontrolu a případnou eliminaci či nahrazení implicitního prvního parametru.
  </handout>

  <p class="enumerate">
    Z předchozích příkladů je vidět, že častého použití by se mohly dekorátory dočkat např. u logovacích nebo debugovacích funkcí:
  </p>
  <ul>
    <li>
        dekorátor pro výpis informací o volaných funkcích;
    </li>
    <li>
        dekorátor pro ukládání informací o volaných funkcích;
    </li>
    <li>
        ...
    </li>
  </ul>

  <p class="enumerate">
    Zcela typicky se s dekorátory setkáte v různých webových frameworcích, kde slouží k zajištění vybraných podmínek pro vstupní nebo návratové hodnoty funkcí. Typické příklady:
  </p>
  <ul>
    <li>
        funkci je možné vykonat pouze tehdy, je-li daný uživatel přihlášen do systému;
    </li>
    <li>
        funkce musí v daném kontextu vracet řetězce bez formátovacích prvků;
    </li>
    <li>
        ...
    </li>
  </ul>

  <p class="enumerate">
    Kupříkladu v kontextu <a href="annotations.xml">anotací funkcí</a> je možno dekorátory použít pro tak nepythoní věc jako je <a href="annotations.xml?slajd=7">ověřování vstupních a výstupních typů</a>.
  </p>

</slide>
<slide title="Knihovna dekorátorů">

    <p>
        Když se podíváte na seznam vestavěných funkcí Python'u (třeba přes <code>dir(__builtins__)</code>), zjistíte kupříkladu, že známé třídní dekorátory <code>@classmethod</code> a <code>@staticmethod</code> jsou prostě jenom funkce v globálním jmenném prostoru. Další dekorátory se schovávají v různých modulech, např. <code>@contextlib.contextmanager</code>. Vzhledem k tomu, že dekorátory jsou vlastně „jenom“ funkce, není se moc čemu divit.
    </p>
    <p>
        Pokud by vás ale zajímal nějaký ultimativnější přehled dekorátorů (převážně externích), podívejte se na oficiální <a href="https://wiki.python.org/moin/PythonDecoratorLibrary" class="external">Python Decorator Library</a>. Najdete tam spoustu zajímavého, mimo jiné i podstatně rozumnější dekorátory pro deskriptory, než je <a href="/materialy/python/objects/descriptors.xml?slajd=4">oficiální verze</a>.
    </p>

</slide>
<slide title="Poznámka k otisku funkce &amp; „@functools.wraps“">

  <p>
    Při použití dekorátorů je třeba pamatovat na to, že „obalovací“ mezifunkce <em>zcela nahradí</em> dekorovanou funkci. Což mimo jiné znamená, že pokud budeme dekorované funkci pomocí introspekce klást nějaké otázky o její identitě, dostaneme úplně jiné odpovědi – budou se totiž týkat identity dekorátoru!
  </p>
  
  <p>
    Jinými slovy – pokud se nebudete (hodně) snažit, použití dekorátoru zcela změní tzv. <strong>otisk funkce</strong> (<em>call signature</em>), tj. přibližně jméno funkce a seznam jejích argumentů.
  </p>
  <note>
    Přepsat úplně celý otisk je dosti komplikované (pro jeho definici viz <a href="http://www.python.org/dev/peps/pep-0362/" class="external">PEP 362</a>). Ale pro začátek se můžete podívat třeba na kopírování dokumentačního řetězce <em>__doc__</em> nebo jména funkce <em>__name__</em>. A možná postačí dekorátor (sic :-) <a href="https://docs.python.org/3/library/functools.html?highlight=wraps#functools.wraps" class="external"><code>functools.wraps</code></a>:
    <example lang="python">
      from functools import wraps

      def dekorátor(funkce):
          @wraps(funkce)
          def _dekorátor(*args, **kwargs):
              ...
          return _dekorátor
    </example>
  </note>

</slide>
<slide title="Poznámka">

    <p>
        Kromě běžného zápisu, který jsme celou dobu používali..
    </p>
    <example lang="python">
      def dekorátor(funkce):
          def _dekorátor(*args, **kwargs):
              ...
          return _dekorátor
    </example>
    <p>
        ..můžete tu vnitřní funkci klidně zapsat i bokem..
    </p>
    <example lang="python">
      def _dekorátor(*args, **kwargs):
          ...

      def dekorátor(funkce):
          return _dekorátor
    </example>
    <p>
        ..ale je vidět, že to je spíš pro zmatení nepřítele než k běžnému užitku.
    </p>

</slide>
<slide title="Poznámka k dekorování generátorů">

    <p>
        Jelikož <a href="/materialy/python/generators/generators.xml">generátory</a> se v Python'u zavádějí pomocí funkcí, dají se také odekorovat. Mezi generátory a funkcemi je však několik významných rozdílů, především:
    </p>
    <ul>
        <li>
            Funkce se při každém svém volání provede celá od začátku do konce, vrátí návratovou hodnotu a ukončí se tím.
        </li>
        <li>
            Generátor se provádí po částech, volá se opakovaně, pamatuje si stav předchozího „zamražení“ a ukončí se vyhozením výjimky <em>StopIteration</em>.
        </li>
    </ul>
    <p>
        Z toho vyplývá, že ne všechno, co jsme si ukázali na předchozích slajdech půjde aplikovat úplně přímočaře i pro generátory – <strong>zatímco dekorátor funkce je sám funkcí, dekorátor generátoru musí být sám generátorem</strong>.
    </p>
    <note>
        Především tedy jedno konkrétní volání dekorované funkce se musí nahradit nějakou složitější konstrukcí, která generátor projde <em>yield</em> po <em>yield</em>u, až ho celý vyčerpá.
    </note>

</slide>


</lecture>
