<?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>Generátory</title>
  <date>2011-04-28</date>
  <link><!--a href="http://vyuka.ookami.cz" rel="external">http://vyuka.ookami.cz</a--></link>
</meta>
<!--
  „“–
  ↵ aneb &#x21B5; aneb \r aneb CR aneb CarriageReturn
-->


<slide title="Úvod">

  <p>
    Naprosto typickým prvkem funkcionálního programování je vytváření (potenciálně i nekonečných) struktur, z nichž se vrací pouze ta část, která je aktuálně vyžadována, a nic dalšího se (zatím) nepočítá.
  </p>
  <p>
    V Python'u se funkcionálním konstruktorům takových objektů říká <strong>generátory</strong> (v objektové variantě pak <a href="/materialy/python/iterators/iterators.xml"><em>iterátory</em></a>) a vyrábějí se z obyčejných funkcí doplněním alespoň jednoho klíčového slova <code>yield</code>.
  </p>

</slide>
<slide title="Funkce „next()“ a výjimka „StopIteration“">

    <p>
        Pro průchod generátorem se principielně používá metoda <code>next()</code>:
    </p>
    <example lang="python">
# zavedení generátoru
>>> def generátor():
...     yield 'první volání'
...     yield 'druhé volání'
>>> g = generátor()

# průchod generátorem
>>> next(g)
'první volání'
>>> next(g)
'druhé volání'
>>> next(g)
Traceback (most recent call last):
  File "&lt;pyshell#117&gt;", line 1, in &lt;module&gt;
    next(g)
StopIteration
    </example>
    <ul>
        <li>
            Každé <code>yield</code> „zmrazí“ funkci v aktuálním stavu, a tak při příštím zavolání generátoru se řízení vrátí přesně do tohoto stavu (narozdíl od funkce, která se „spustí“ znovu od začátku).
        </li>
        <li>
            Dorazí-li generátor na svůj konec (tedy nemůže-li již daným předpisem vygenerovat další člen posloupnosti), vyhodí výjimku <code>StopIteration</code>.
        </li>
    </ul>

</slide>
<slide title="PS: Hlášení konce „next()“ bez výjimky">

    <p>
        Jsou chvíle, kdy je vyhození výjimky funkcí <em>next()</em> nežádoucí (například při načítání souboru pomocí smyčky <em>while</em>). V takové situaci je možno toto chování přepsat přidáním druhého parametru funkce, který bude vrácen místo výjimky:
    </p>
    <example lang="python" src="_files/next_default.py" />

</slide>
<slide title="Smyčka „for-in“">

    <p>
        Smyčka <code>for-in</code> se o vytvoření příslušné instance generátoru (jakkoli to zní u funkce protismyslně) i automatické ukončení při zachycení výjimky <code>StopIteration</code> postará sama:
    </p>
  <example lang="python">
>>> def generátor():
...     """Tento generátor je možné zavolat celkem dvakrát."""
...     yield 'první volání'
...     yield 'druhé volání'

>>> for i in generátor():
...     print(i)
první volání
druhé volání
  </example>

</slide>
<slide title="„next()“ a „for-in“ dohromady">

    <p>
        Generátor „v akci“ si s sebou nese svůj aktuální pracovní kontext a smyčka <em>for-in</em> ho „nenaboří“, naopak s ním bude pěkně spolupracovat:
    </p>
    <example lang="python">
>>> def generátor():
...     yield 'první volání'
...     yield 'druhé volání'
...     yield 'třetí volání'
...     yield 'čtvrté volání'
>>> g = generátor()
>>> next(g)
'první volání'
>>> next(g)
'druhé volání'
>>> for x in g:
...     print(x)
třetí volání
čtvrté volání
    </example>

</slide>
<slide title="Automatická versus manuální iterace">

  <p>
    Mezi použitím globální funkce <code>next()</code> pro vyvolání dalšího <em>yield</em>u a smyčkou <em>for-in</em> pro proiterování generátoru je několik rozdílů:
  </p>
  <ul>
    <li>
      <code>next()</code> očekává na vstupu konkrétní instanci generátoru, nikoli jeho konstruktor => kód <code>next( g() )</code> bude vracet pokaždé první <em>yield</em>; pro nejspíše zamýšlené fungování tedy budete chtít provést něco takovéhoto:
      <example lang="python">
        g = generátor()
        next(g)
      </example>
    </li>
    <li>
      podobně zavolání <code>next()</code> na ukončený generátor (tj. ten, který už nemá co vrátit) vyvolá výjimku <code>StopIteration</code>
      <note>
        Což je mimochodem právě ta výjimka, která ukončuje vykonávání smyčky <em>for in</em>.
      </note>
    </li>
  </ul>
  <p>
    Narozdíl od <code>next()</code> si tohle všechno smyčka <em>for-in</em> hlídá sama – na vstup jí můžete poslat rovnou konstruktor generátoru a uvedená výjimka je logicky interpretována jako ukončení cyklu.
  </p>
  <p>
    Na druhou stranu smyčka <em>while</em> nic takového pochopitelně nedělá, takže nepřekvapí, že u ní se můžeme s voláním <code>next()</code> setkat docela často.
  </p>

</slide>
<slide title="Poznámka – metoda „__next__()“">

    <p>
        Volání vestavěné funkce <code>next()</code> způsobí na pozadí zavolání metody <code>__next__()</code> příslušného generátoru:
    </p>
    <example lang="python">
>>> def generátor():
...     yield 'první volání'
...     yield 'druhé volání'
>>> g = generátor()
>>> dir(g)
['__class__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__',
 '__getattribute__', '__gt__', '__hash__', '__init__', '__iter__', '__le__',
 '__lt__', '__name__', '__ne__', '__new__', '__next__', '__reduce__',
 '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
 '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'send',
  'throw']
>>> g.__next__()
'první volání'
>>> next(g)
'druhé volání'
>>> g.__next__()
Traceback (most recent call last):
  File "&lt;pyshell#123&gt;", line 1, in &lt;module&gt;
    g.__next__()
StopIteration
    </example>
    <p>
        Tohle v kódu nebude chtít jen tak použít, ale naznačuje to přímou souvislost s <a href="/materialy/python/iterators/iterators.xml">iterátory</a>.
    </p>

</slide>
<slide title="Co všechno způsobí „next()“">

    <p>
        Zatím jsem moc nezdůraznil, že <strong>zavolaný generátor vždy vykoná veškerý dostupný kód, než se zarazí po vrácení hodnoty vykonáním příslušného následujícího <code>yield</code>'u</strong>:
    </p>
    <example lang="python">
        >>> def generátor():
        ...     print('Start!')
        ...     yield 1
        ...     print('Jsme v generátoru…')
        ...     yield 2
        ...     print('Brzy bude konec.')
        >>> g = generátor()
        
        >>> next(g)
        Start!
        0: 1
        
        >>> next(g)
        Jsme v generátoru…
        1: 2
        
        >>> next(g)
        Brzy bude konec.
        Traceback (most recent call last):
          File "&lt;pyshell#5>", line 1, in &lt;module>
            next(g)
        StopIteration
    </example>
    <p>
        Asi nejdůležitější je si uvědomit, že se to především týká veškerého kódu před prvním <em>yield</em>'em. To znamená, že <strong>zavolání funkce „konstruktoru“ dekorátoru nezačne automaticky vykonávat v ní (resp. v něm) obsažený kód – to obstará až první zavolání funkce <code>next()</code></strong>.
    </p>

</slide>
<slide title="„Konstruktor“ generátoru a jeho parametry">

    <p>
        Přestože funkce obsahující místo <em>return</em> slovo <em>yield</em> fakticky slouží jako <strong>konstruktor generátoru</strong> (aneb jejím zavoláním je vytvořen objekt úplně jiného typu – místo vrácené hodnoty dostaneme generátor), není žádný důvod, proč by nemohla přijímat vstupní parametry. Příklad:
    </p>
    <example lang="python">
        >>> def lichá_čísla(od=1):
        ...     číslo = od - 2
        ...     while True:
        ...         číslo += 2
        ...         yield číslo
         
        >>> g = lichá_čísla(11)
         
        >>> next(g)
        9: 11
        >>> next(g)
        10: 13
    </example>

</slide>
<slide title="Konečné generátory">

    <p>
        Typický generátor někdy skončí, tj. od jistého okamžiku už nebude počítat další hodnoty. Z hlediska příslušné funkce tedy uvedený kód někdy doběhne do svého konce:
    </p>
    <example lang="python">
        # Generátor..
        def reverse(data):
            for index in range(len(data)-1, -1, -1):
                yield data[index]
        
        # ..při použití..
        for char in reverse('Ahoj!'):
            print(char)
        
        # ..vrací:
        !
        j
        o
        h
        A
    </example>
    <notes>
        <note>
            Upraveno podle dokumentace.
        </note>
        <note>
            Zde ukončení zajistí zjevně smyčka <em>for-in</em> přes objekt rozsahu – jakmile se (zjevně konečný) rozsah vyčerpá, cyklus se ukončí, následně se ukončí generátor a vrátí při tom implicitní <em>None</em> (je to totiž pořád funkce, takže <a href="/materialy/python/functions/overview.xml?slajd=3">se chová stejně</a> jako ony).
        </note>
    </notes>

</slide>
<slide title="Konečné generátory – „return“">

    <p>
        Generátor z předchozího slajdu je konečný díky tomu, že funkce použitá k jeho vytvoření někdy skončí. Přitom konec této funkce je navenek signalizován tím, že <em>se z funkce vrátí hodnota <code>None</code></em>.
    </p>
    <p>
        Ačkoli v přechozím příkladě bylo ono vrácení implicitní, můžeme generátor – možná trochu překvapivě – ukončit také explicitním vrácením se z něj pomocí <code>return HODNOTA</code>. Uvedená hodnota je přitom předána jako atribut <code>value</code> příslušné výjimky <em>StopIteration</em>:
    </p>
    <example layout="horizontal">
        <program src="_files/generators.return.py" lang="python"/>
        <out src="_files/generators.return.out" lang="text"/>
    </example>
    <note>
        A na poslední <em>yield</em> se nepřekvapivě nedostane.
    </note>

</slide>
<slide title="Konečné generátory – „close()“">

    <p>
        Ale jde to i jinak – generátory podporují své explicitní ukončení také pomocí metody <code>close()</code>:
    </p>
    <example lang="python">
        >>> def lichá_čísla(od):
        ...     číslo = od - 2
        ...     while True:
        ...         číslo += 2
        ...         yield číslo
         
        >>> g = lichá_čísla(11)
         
        >>> next(g)
        9: 11
        >>> next(g)
        10: 13
         
        >>> g.close()
        
        >>> next(g)
        Traceback (most recent call last):
          File "&lt;pyshell#33>", line 1, in &lt;module>
            next(g)
        StopIteration
    </example>

</slide>
<slide title="Nekonečné generátory">

    <p>
        Jak jsme <a href="?slajd=8">už viděli</a>, jedna z mnoha výhod generátorů tkví v tom, že mohou být potenciálně i nekonečné, aniž bychom se museli bát o dostupnou paměť – počítá (a vrací) se z nich jenom to, co je v danou chvíli skutečně potřeba:
    </p>
    <example lang="python">
        >>> def sudá_čísla():
        ...     číslo = 0
        ...     while True:
        ...         číslo += 2
        ...         yield číslo
        
        >>> g = sudá_čísla()
        
        >>> next(g)
        2
        >>> next(g)
        4
        >>> next(g)
        6
    </example>
    <note>
        Samozřejmě si pak musíte dát pozor, abyste se nechytili v nekonečné smyčce ^_^
    </note>

</slide>
<!--slide title="">

    <p>
    </p>
    <example lang="python">
    </example>

</slide-->
<slide title="Poznámka – „yield from“">

    <p>
        V Python'u 3.3 přibyla možnost použít konstrukci:
    </p>
    <example lang="python">
        yield from GENERÁTOR
    </example>
    <p>
        Tato slouží (alespoň než se zamotáme do <a href="coroutines.xml?slajd=6">korutin</a>) k odkázání získání návratové hodnoty na sub-generátor:
    </p>
    <example layout="horizontal">
        <program src="_files/generators.1.py" lang="python"/>
        <out src="_files/generators.1+2.out" lang="text"/>
    </example>
    <br/>
    <example layout="horizontal">
        <program src="_files/generators.2.py" lang="python"/>
        <out src="_files/generators.1+2.out" lang="text"/>
    </example>
    <note>
        Možná to není na první pohled zřejmé, ale takto můžeme snadno generátory řetězit.
    </note>

</slide>


</lecture>
