Generátory

Dnes si popíšeme, jak v Pythonu fungují generátory, tedy funkce s příkazem yield. Někteří z vás možná už nějaký jednoduchý generátor napsali, ale pojďme si je vysvětlit od úplného začátku: od toho, jak se v Pythonu iteruje.

Iterace

Když je v Pythonu potřeba iterovat (např. příkazem for) přes nějakou kolekci (seznam, řetězec, soubor, …), použije se iterační protokol, který pracuje se dvěma druhy objektů: s iterovatelnými objekty a s iterátory.

Iterovatelné objekty (iterables) se vyznačují tím, že je na ně možné zavolat funkci iter(). Ta vrátí příslušný iterátor:

>>> iter([1, 2, 3])
<list_iterator object at 0x...>

Na iterátor pak je možné opakovaně volat funkci next(), čímž dostáváme jednotlivé prvky iterace. Po vyčerpání iterátoru způsobuje next() výjimku StopIteration:

>>> it = iter([1, 2, 3])
>>> next(it)
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
  ...
StopIteration
>>> next(it)
Traceback (most recent call last):
  ...
StopIteration

Cyklus for x in collection: ... tedy dělá něco jako:

iterator = iter(collection)
while True:
    try:
        x = next(iterator)
    except StopIteration:
        break

    ...  # tělo původního cyklu

Platí, že každý iterátor je iterovatelný: zavoláním iter() na iterátor dostaneme ten stejný iterátor (nikoli jeho kopii) zpět. Naopak to ale obecně neplatí: např. seznamy jsou iterovatelné, ale nejsou samy o sobě iterátory. Každé zavolání iter(seznam) vrací nový iterátor, který má svou vlastní „aktuální pozici“ a iteruje od začátku.

Iterátor je ve většině případů „malý“ objekt, který si „pamatuje“ jen původní iterovatelný objekt a aktuální pozici. Příklady jsou iterátory seznamů (iter([])), slovníků (iter({})), n-tic nebo množin, iterátor pro range a podobně.

Iterátory ale můžou být i „větší“: třeba otevřený soubor je iterátor, z něhož next() načítá jednotlivé řádky.

Generátory

Asi nejzajímavější druh iterátoru je tzv. generátor: funkce, která umí postupně dávat k dispozici hodnoty. Definuje se pomocí klíčového slova yield: každá funkce, která obsahuje yield, je generátorová funkce (angl. generator function).

def generate2():
    """generates 2 numbers"""
    print('A')
    yield 0
    print('B')
    yield 1
    print('C')

Zavoláním takové funkce dostáváme generátorový iterátor (angl. generator iterator):

>>> generate2()
<generator object generate2 at 0x...>

Voláním next() se pak stane zajímavá věc: funkce se provede až po první yield, tam se zastaví a hodnota yield-u se vrátí z next(). Při dalším volání se začne provádět zbytek funkce od místa, kde byla naposled zastavena.

>>> it = generate2()
>>> next(it)
A
0
>>> next(it)
B
1
>>> next(it)
C
Traceback (most recent call last):
  ...
StopIteration

Další použití generátorů

Vlastnost přerušit provádění funkce je velice užitečná nejen pro vytváření sekvencí, ale má celou řadu dalších užití. Existuje třeba dekorátor, který generátorovou funkci s jedním yield převede na context manager, tedy objekt použitelný s příkazem with:

import contextlib

@contextlib.contextmanager
def ctx_manager():
    print('Entering')
    yield 123
    print('Exiting')


with ctx_manager() as obj:
    print('Inside context, with', obj)

Vše před yield se provede při vstupu do kontextu, hodnota yield se předá dál a vše po yield se provede na konci. Můžeme si představit, že místo yield se „doplní“ obsah bloku with. Funkce se tam na chvíli zastaví a může se tedy provádět něco jiného.

Vracení hodnot z generátorů

V rámci generátorové funkce můžeme použít i return, který funkci ukončí. Vrácená hodnota se však při normální iteraci (např. ve for) nepoužije. Objeví se pouze jako hodnota výjimky StopIteration, která signalizuje konec iterace:

def generator(a, b):
    """Yield two numbers and return their sum"""
    yield a
    yield b
    return a + b
>>> it = generator(2, 3)
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration: 5

Obousměrná komunikace

Oproti normálním iterátorům, které hodnoty jen poskytují, mají generátory metodu send(), kterou je možné posílat hodnoty do běžícího generátoru. Klíčové slovo yield totiž může fungovat jako výraz a tento výraz nabývá poslanou hodnotu (nebo None, byl-li použit normální next()).

def running_sum():
    total = 0
    while True:
        num = (yield total)
        if num:
            total += num

it = running_sum()
next(it)  # pro první iteraci nelze použít send() -- nečekáme zatím na yield-u
it.send(2)
it.send(3)
assert next(it) == 5

Upřímě řečeno, metoda send() není příliš užitečná. (Když byste něco takového potřebovali, radši si napište třídu, která si bude stav uchovávat v atributech, a měňte ji třeba metodami. Bude to pravděpodobně přehlednější.) Existuje ale příbuzná metoda, která už je užitečnější: throw(). Ta do generátoru „vhodí“ výjimku. Z pohledu generátorové funkce to vypadá, jako by výjimka nastala na příkazu yield.

def report_exception():
    try:
        yield
    except BaseException as e:
        print('Death by', type(e).__name__)
    yield 123
>>> it = report_exception()
>>> next(it)  # opět – v první iteraci nelze throw() použít
>>> value = it.throw(ValueError())
Death by ValueError
>>> value
123

Podobná věc se děje, když generátorový iterátor zanikne: Python do generátoru „vhodí“ výjimku GeneratorExit. Ta dědí z BaseException, ale ne Exception, takže klasické except Exception: ji nechytí (ale např. finally funguje jak má). Pokud generátor tuto výjimku chytá, měl by se co nejdřív ukončit. (Když to neudělá a provede další yield, Python ho ukončí „násilně“.)

>>> import gc
>>> it = report_exception()
>>> next(it)
>>> del it; gc.collect()  # zbavíme se objektu "it"
Death by GeneratorExit
Exception ignored in: <generator object report_exception at 0x...>
RuntimeError: generator ignored GeneratorExit
0

Delegace na podgenerátor – yield from

Máme následující generátor:

def dance():
    yield 'putting hands forward'
    yield 'putting hands down'
    yield 'turning around'
    yield 'jumping'
    yield 'putting hands forward'
    yield 'putting hands down'

for action in dance():
    print(action)

Opakuje se v něm jistá sekvence, kterou bychom jako správní programátoři chtěli vyčlenit do samostatné funkce. Pomocí samotného yield to ale jde celkem těžko:

def dance_hands():
    yield 'putting hands forward'
    yield 'putting hands down'

def dance():
    for action in dance_hands():
        yield action
    yield 'turning around'
    yield 'jumping'
    for action in dance_hands():
        yield action

for action in dance():
    print(action)

Tohle počtu řádků příliš nepomohlo. Existuje lepší způsob – místo cyklu můžeme použít yield from:

def dance_hands():
    yield 'putting hands forward'
    yield 'putting hands down'

def dance():
    yield from dance_hands()
    yield 'turning around'
    yield 'jumping'
    yield from dance_hands()

for action in dance():
    print(action)

Navíc lze yield from použít jako výraz, který nabývá hodnoty kterou podgenerátor vrátil (t.j. hodnota výjimky StopIteration).

def fibonacci(limit):
    a = 0
    b = 1
    count = 0
    while b < limit:
        yield b
        count += 1
        a, b = b, a + b
    return count

def fib_annotated():
    count = (yield from fibonacci(10))
    print('LOG: Generated {} numbers'.format(count))

for value in fib_annotated():
    print('Got', value)
Got 1
Got 1
Got 2
Got 3
Got 5
Got 8
LOG: Generated 6 numbers

Kromě toho yield from deleguje hodnoty poslané pomocí send() či throw().

{
  "data": {
    "sessionMaterial": {
      "id": "session-material:2017/mipyt-zima:async:0",
      "title": "GenerĂ¡tory",
      "html": "\n          \n    \n\n    <h1>Gener&#xE1;tory</h1>\n<p>Dnes si pop&#xED;&#x161;eme, jak v Pythonu funguj&#xED; <em>gener&#xE1;tory</em>, tedy funkce s p&#x159;&#xED;kazem <code>yield</code>.\nN&#x11B;kte&#x159;&#xED; z&#xA0;v&#xE1;s mo&#x17E;n&#xE1; u&#x17E; n&#x11B;jak&#xFD; jednoduch&#xFD; gener&#xE1;tor napsali, ale poj&#x10F;me si je\nvysv&#x11B;tlit od &#xFA;pln&#xE9;ho za&#x10D;&#xE1;tku: od toho, jak se v Pythonu iteruje.</p>\n<h2>Iterace</h2>\n<p>Kdy&#x17E; je v Pythonu pot&#x159;eba iterovat (nap&#x159;. p&#x159;&#xED;kazem <code>for</code>) p&#x159;es n&#x11B;jakou kolekci\n(seznam, &#x159;et&#x11B;zec, soubor, &#x2026;), pou&#x17E;ije se <em>itera&#x10D;n&#xED; protokol</em>,\nkter&#xFD; pracuje se dv&#x11B;ma druhy objekt&#x16F;: s <em>iterovateln&#xFD;mi objekty</em> a s <em>iter&#xE1;tory</em>.</p>\n<p>Iterovateln&#xE9; objekty (<em>iterables</em>) se vyzna&#x10D;uj&#xED; t&#xED;m, &#x17E;e je na n&#x11B; mo&#x17E;n&#xE9; zavolat\nfunkci <code>iter()</code>. Ta vr&#xE1;t&#xED; p&#x159;&#xED;slu&#x161;n&#xFD; iter&#xE1;tor:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">&gt;&gt;&gt; </span><span class=\"nb\">iter</span><span class=\"p\">([</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">])</span>\n<span class=\"go\">&lt;list_iterator object at 0x...&gt;</span>\n</pre></div><p>Na iter&#xE1;tor pak je mo&#x17E;n&#xE9; opakovan&#x11B; volat funkci <code>next()</code>, &#x10D;&#xED;m&#x17E; dost&#xE1;v&#xE1;me jednotliv&#xE9;\nprvky iterace.\nPo vy&#x10D;erp&#xE1;n&#xED; iter&#xE1;toru zp&#x16F;sobuje <code>next()</code> v&#xFD;jimku <code>StopIteration</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"nb\">iter</span><span class=\"p\">([</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">])</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>\n<span class=\"go\">1</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>\n<span class=\"go\">2</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>\n<span class=\"go\">3</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>\n<span class=\"gt\">Traceback (most recent call last):</span>\n  <span class=\"c\">...</span>\n<span class=\"gr\">StopIteration</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>\n<span class=\"gt\">Traceback (most recent call last):</span>\n  <span class=\"c\">...</span>\n<span class=\"gr\">StopIteration</span>\n</pre></div><p>Cyklus <code>for x in collection: ...</code> tedy d&#x11B;l&#xE1; n&#x11B;co jako:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">iterator</span> <span class=\"o\">=</span> <span class=\"nb\">iter</span><span class=\"p\">(</span><span class=\"n\">collection</span><span class=\"p\">)</span>\n<span class=\"k\">while</span> <span class=\"bp\">True</span><span class=\"p\">:</span>\n    <span class=\"k\">try</span><span class=\"p\">:</span>\n        <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">iterator</span><span class=\"p\">)</span>\n    <span class=\"k\">except</span> <span class=\"ne\">StopIteration</span><span class=\"p\">:</span>\n        <span class=\"k\">break</span>\n\n    <span class=\"o\">...</span>  <span class=\"c1\"># t&#x11B;lo p&#x16F;vodn&#xED;ho cyklu</span>\n</pre></div><p>Plat&#xED;, &#x17E;e ka&#x17E;d&#xFD; iter&#xE1;tor je iterovateln&#xFD;: zavol&#xE1;n&#xED;m <code>iter()</code> na iter&#xE1;tor\ndostaneme ten stejn&#xFD; iter&#xE1;tor (nikoli jeho kopii) zp&#x11B;t.\nNaopak to ale obecn&#x11B; neplat&#xED;: nap&#x159;. seznamy jsou iterovateln&#xE9;, ale nejsou samy\no sob&#x11B; iter&#xE1;tory.\nKa&#x17E;d&#xE9; zavol&#xE1;n&#xED; <code>iter(seznam)</code> vrac&#xED; nov&#xFD; iter&#xE1;tor, kter&#xFD; m&#xE1; svou vlastn&#xED;\n&#x201E;aktu&#xE1;ln&#xED; pozici&#x201C; a iteruje od za&#x10D;&#xE1;tku.</p>\n<p>Iter&#xE1;tor je ve v&#x11B;t&#x161;in&#x11B; p&#x159;&#xED;pad&#x16F; &#x201E;mal&#xFD;&#x201C; objekt, kter&#xFD; si &#x201E;pamatuje&#x201C; jen p&#x16F;vodn&#xED; iterovateln&#xFD;\nobjekt a aktu&#xE1;ln&#xED; pozici. P&#x159;&#xED;klady jsou iter&#xE1;tory seznam&#x16F; (<code>iter([])</code>), slovn&#xED;k&#x16F; (<code>iter({})</code>),\n<em>n</em>-tic nebo mno&#x17E;in, iter&#xE1;tor pro <code>range</code> a podobn&#x11B;.</p>\n<p>Iter&#xE1;tory ale m&#x16F;&#x17E;ou b&#xFD;t i &#x201E;v&#x11B;t&#x161;&#xED;&#x201C;: t&#x159;eba otev&#x159;en&#xFD; soubor je iter&#xE1;tor, z&#xA0;n&#x11B;ho&#x17E; <code>next()</code>\nna&#x10D;&#xED;t&#xE1; jednotliv&#xE9; &#x159;&#xE1;dky.</p>\n<h2>Gener&#xE1;tory</h2>\n<p>Asi nejzaj&#xED;mav&#x11B;j&#x161;&#xED; druh iter&#xE1;toru je tzv. <em>gener&#xE1;tor</em>: funkce, kter&#xE1; um&#xED; postupn&#x11B;\nd&#xE1;vat k dispozici hodnoty.\nDefinuje se pomoc&#xED; kl&#xED;&#x10D;ov&#xE9;ho slova <code>yield</code>: ka&#x17E;d&#xE1; funkce, kter&#xE1; obsahuje <code>yield</code>,\nje <em>gener&#xE1;torov&#xE1; funkce</em> (angl. <em>generator function</em>).</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">generate2</span><span class=\"p\">():</span>\n    <span class=\"sd\">&quot;&quot;&quot;generates 2 numbers&quot;&quot;&quot;</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">&apos;A&apos;</span><span class=\"p\">)</span>\n    <span class=\"k\">yield</span> <span class=\"mi\">0</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">&apos;B&apos;</span><span class=\"p\">)</span>\n    <span class=\"k\">yield</span> <span class=\"mi\">1</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">&apos;C&apos;</span><span class=\"p\">)</span>\n</pre></div><p>Zavol&#xE1;n&#xED;m takov&#xE9; funkce dost&#xE1;v&#xE1;me <em>gener&#xE1;torov&#xFD; iter&#xE1;tor</em> (angl. <em>generator iterator</em>):</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">generate2</span><span class=\"p\">()</span>\n<span class=\"go\">&lt;generator object generate2 at 0x...&gt;</span>\n</pre></div><p>Vol&#xE1;n&#xED;m <code>next()</code> se pak stane zaj&#xED;mav&#xE1; v&#x11B;c: funkce se provede a&#x17E; po prvn&#xED; <code>yield</code>,\ntam se <em>zastav&#xED;</em> a hodnota <code>yield</code>-u se vr&#xE1;t&#xED; z <code>next()</code>.\nP&#x159;i dal&#x161;&#xED;m vol&#xE1;n&#xED; se za&#x10D;ne prov&#xE1;d&#x11B;t zbytek funkce od m&#xED;sta, kde byla naposled\nzastavena.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"n\">generate2</span><span class=\"p\">()</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>\n<span class=\"go\">A</span>\n<span class=\"go\">0</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>\n<span class=\"go\">B</span>\n<span class=\"go\">1</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>\n<span class=\"go\">C</span>\n<span class=\"gt\">Traceback (most recent call last):</span>\n  <span class=\"c\">...</span>\n<span class=\"gr\">StopIteration</span>\n</pre></div><h2>Dal&#x161;&#xED; pou&#x17E;it&#xED; gener&#xE1;tor&#x16F;</h2>\n<p>Vlastnost p&#x159;eru&#x161;it prov&#xE1;d&#x11B;n&#xED; funkce je velice u&#x17E;ite&#x10D;n&#xE1; nejen pro vytv&#xE1;&#x159;en&#xED;\nsekvenc&#xED;, ale m&#xE1; celou &#x159;adu dal&#x161;&#xED;ch u&#x17E;it&#xED;.\nExistuje t&#x159;eba dekor&#xE1;tor, kter&#xFD; gener&#xE1;torovou funkci s jedn&#xED;m <code>yield</code> p&#x159;evede na <em>context manager</em>,\ntedy objekt pou&#x17E;iteln&#xFD; s&#xA0;p&#x159;&#xED;kazem <code>with</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">import</span> <span class=\"nn\">contextlib</span>\n\n<span class=\"nd\">@contextlib.contextmanager</span>\n<span class=\"k\">def</span> <span class=\"nf\">ctx_manager</span><span class=\"p\">():</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">&apos;Entering&apos;</span><span class=\"p\">)</span>\n    <span class=\"k\">yield</span> <span class=\"mi\">123</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">&apos;Exiting&apos;</span><span class=\"p\">)</span>\n\n\n<span class=\"k\">with</span> <span class=\"n\">ctx_manager</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span class=\"n\">obj</span><span class=\"p\">:</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">&apos;Inside context, with&apos;</span><span class=\"p\">,</span> <span class=\"n\">obj</span><span class=\"p\">)</span>\n</pre></div><p>V&#x161;e p&#x159;ed <code>yield</code> se provede p&#x159;i vstupu do kontextu, hodnota <code>yield</code> se p&#x159;ed&#xE1;\nd&#xE1;l a v&#x161;e po <code>yield</code> se provede na konci.\nM&#x16F;&#x17E;eme si p&#x159;edstavit, &#x17E;e m&#xED;sto <code>yield</code> se &#x201E;dopln&#xED;&#x201C; obsah bloku <code>with</code>.\nFunkce se tam na chv&#xED;li zastav&#xED; a m&#x16F;&#x17E;e se tedy prov&#xE1;d&#x11B;t n&#x11B;co jin&#xE9;ho.</p>\n<h2>Vracen&#xED; hodnot z gener&#xE1;tor&#x16F;</h2>\n<p>V r&#xE1;mci gener&#xE1;torov&#xE9; funkce m&#x16F;&#x17E;eme pou&#x17E;&#xED;t i <code>return</code>, kter&#xFD; funkci ukon&#x10D;&#xED;.\nVr&#xE1;cen&#xE1; hodnota se v&#x161;ak p&#x159;i norm&#xE1;ln&#xED; iteraci (nap&#x159;. ve <code>for</code>) nepou&#x17E;ije.\nObjev&#xED; se pouze jako hodnota v&#xFD;jimky <code>StopIteration</code>, kter&#xE1; signalizuje konec\niterace:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">generator</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"p\">,</span> <span class=\"n\">b</span><span class=\"p\">):</span>\n    <span class=\"sd\">&quot;&quot;&quot;Yield two numbers and return their sum&quot;&quot;&quot;</span>\n    <span class=\"k\">yield</span> <span class=\"n\">a</span>\n    <span class=\"k\">yield</span> <span class=\"n\">b</span>\n    <span class=\"k\">return</span> <span class=\"n\">a</span> <span class=\"o\">+</span> <span class=\"n\">b</span>\n</pre></div><div class=\"highlight\"><pre><span></span><span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"n\">generator</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">)</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>\n<span class=\"go\">2</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>\n<span class=\"go\">3</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>\n<span class=\"gt\">Traceback (most recent call last):</span>\n  File <span class=\"nb\">&quot;&lt;stdin&gt;&quot;</span>, line <span class=\"m\">1</span>, in <span class=\"n\">&lt;module&gt;</span>\n<span class=\"gr\">StopIteration</span>: <span class=\"n\">5</span>\n</pre></div><h2>Obousm&#x11B;rn&#xE1; komunikace</h2>\n<p>Oproti norm&#xE1;ln&#xED;m iter&#xE1;tor&#x16F;m, kter&#xE9; hodnoty jen poskytuj&#xED;, maj&#xED; gener&#xE1;tory metodu\n<code>send()</code>, kterou je mo&#x17E;n&#xE9; pos&#xED;lat hodnoty <em>do</em> b&#x11B;&#x17E;&#xED;c&#xED;ho gener&#xE1;toru.\nKl&#xED;&#x10D;ov&#xE9; slovo <code>yield</code> toti&#x17E; m&#x16F;&#x17E;e fungovat jako v&#xFD;raz a tento v&#xFD;raz nab&#xFD;v&#xE1; poslanou\nhodnotu (nebo <code>None</code>, byl-li pou&#x17E;it norm&#xE1;ln&#xED; <code>next()</code>).</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">running_sum</span><span class=\"p\">():</span>\n    <span class=\"n\">total</span> <span class=\"o\">=</span> <span class=\"mi\">0</span>\n    <span class=\"k\">while</span> <span class=\"bp\">True</span><span class=\"p\">:</span>\n        <span class=\"n\">num</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"k\">yield</span> <span class=\"n\">total</span><span class=\"p\">)</span>\n        <span class=\"k\">if</span> <span class=\"n\">num</span><span class=\"p\">:</span>\n            <span class=\"n\">total</span> <span class=\"o\">+=</span> <span class=\"n\">num</span>\n\n<span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"n\">running_sum</span><span class=\"p\">()</span>\n<span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>  <span class=\"c1\"># pro prvn&#xED; iteraci nelze pou&#x17E;&#xED;t send() -- ne&#x10D;ek&#xE1;me zat&#xED;m na yield-u</span>\n<span class=\"n\">it</span><span class=\"o\">.</span><span class=\"n\">send</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span>\n<span class=\"n\">it</span><span class=\"o\">.</span><span class=\"n\">send</span><span class=\"p\">(</span><span class=\"mi\">3</span><span class=\"p\">)</span>\n<span class=\"k\">assert</span> <span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span> <span class=\"o\">==</span> <span class=\"mi\">5</span>\n</pre></div><p>Up&#x159;&#xED;m&#x11B; &#x159;e&#x10D;eno, metoda <code>send()</code> nen&#xED; p&#x159;&#xED;li&#x161; u&#x17E;ite&#x10D;n&#xE1;.\n(Kdy&#x17E; byste n&#x11B;co takov&#xE9;ho pot&#x159;ebovali, rad&#x161;i si napi&#x161;te t&#x159;&#xED;du, kter&#xE1; si bude\nstav uchov&#xE1;vat v atributech, a m&#x11B;&#x148;te ji t&#x159;eba metodami. Bude to pravd&#x11B;podobn&#x11B;\np&#x159;ehledn&#x11B;j&#x161;&#xED;.)\nExistuje ale p&#x159;&#xED;buzn&#xE1; metoda, kter&#xE1; u&#x17E; je u&#x17E;ite&#x10D;n&#x11B;j&#x161;&#xED;: <code>throw()</code>.\nTa do gener&#xE1;toru &#x201E;vhod&#xED;&#x201C; v&#xFD;jimku.\nZ pohledu gener&#xE1;torov&#xE9; funkce to vypad&#xE1;, jako by v&#xFD;jimka nastala na p&#x159;&#xED;kazu\n<code>yield</code>.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">report_exception</span><span class=\"p\">():</span>\n    <span class=\"k\">try</span><span class=\"p\">:</span>\n        <span class=\"k\">yield</span>\n    <span class=\"k\">except</span> <span class=\"ne\">BaseException</span> <span class=\"k\">as</span> <span class=\"n\">e</span><span class=\"p\">:</span>\n        <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">&apos;Death by&apos;</span><span class=\"p\">,</span> <span class=\"nb\">type</span><span class=\"p\">(</span><span class=\"n\">e</span><span class=\"p\">)</span><span class=\"o\">.</span><span class=\"vm\">__name__</span><span class=\"p\">)</span>\n    <span class=\"k\">yield</span> <span class=\"mi\">123</span>\n</pre></div><div class=\"highlight\"><pre><span></span><span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"n\">report_exception</span><span class=\"p\">()</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>  <span class=\"c1\"># op&#x11B;t &#x2013; v prvn&#xED; iteraci nelze throw() pou&#x17E;&#xED;t</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">value</span> <span class=\"o\">=</span> <span class=\"n\">it</span><span class=\"o\">.</span><span class=\"n\">throw</span><span class=\"p\">(</span><span class=\"ne\">ValueError</span><span class=\"p\">())</span>\n<span class=\"go\">Death by ValueError</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">value</span>\n<span class=\"go\">123</span>\n</pre></div><p>Podobn&#xE1; v&#x11B;c se d&#x11B;je, kdy&#x17E; gener&#xE1;torov&#xFD; iter&#xE1;tor zanikne: Python do gener&#xE1;toru\n&#x201E;vhod&#xED;&#x201C; v&#xFD;jimku GeneratorExit.\nTa d&#x11B;d&#xED; z <code>BaseException</code>, ale ne <code>Exception</code>, tak&#x17E;e klasick&#xE9; <code>except Exception:</code>\nji nechyt&#xED; (ale nap&#x159;. <code>finally</code> funguje jak m&#xE1;).\nPokud gener&#xE1;tor tuto v&#xFD;jimku chyt&#xE1;, m&#x11B;l by se co nejd&#x159;&#xED;v ukon&#x10D;it.\n(Kdy&#x17E; to neud&#x11B;l&#xE1; a provede dal&#x161;&#xED; <code>yield</code>, Python ho ukon&#x10D;&#xED; &#x201E;n&#xE1;siln&#x11B;&#x201C;.)</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">&gt;&gt;&gt; </span><span class=\"kn\">import</span> <span class=\"nn\">gc</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"n\">report_exception</span><span class=\"p\">()</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"k\">del</span> <span class=\"n\">it</span><span class=\"p\">;</span> <span class=\"n\">gc</span><span class=\"o\">.</span><span class=\"n\">collect</span><span class=\"p\">()</span>  <span class=\"c1\"># zbav&#xED;me se objektu &quot;it&quot;</span>\n<span class=\"go\">Death by GeneratorExit</span>\n<span class=\"go\">Exception ignored in: &lt;generator object report_exception at 0x...&gt;</span>\n<span class=\"go\">RuntimeError: generator ignored GeneratorExit</span>\n<span class=\"go\">0</span>\n</pre></div><h2>Delegace na podgener&#xE1;tor &#x2013; <code>yield from</code></h2>\n<p>M&#xE1;me n&#xE1;sleduj&#xED;c&#xED; gener&#xE1;tor:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">dance</span><span class=\"p\">():</span>\n    <span class=\"k\">yield</span> <span class=\"s1\">&apos;putting hands forward&apos;</span>\n    <span class=\"k\">yield</span> <span class=\"s1\">&apos;putting hands down&apos;</span>\n    <span class=\"k\">yield</span> <span class=\"s1\">&apos;turning around&apos;</span>\n    <span class=\"k\">yield</span> <span class=\"s1\">&apos;jumping&apos;</span>\n    <span class=\"k\">yield</span> <span class=\"s1\">&apos;putting hands forward&apos;</span>\n    <span class=\"k\">yield</span> <span class=\"s1\">&apos;putting hands down&apos;</span>\n\n<span class=\"k\">for</span> <span class=\"n\">action</span> <span class=\"ow\">in</span> <span class=\"n\">dance</span><span class=\"p\">():</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">action</span><span class=\"p\">)</span>\n</pre></div><p>Opakuje se v n&#x11B;m jist&#xE1; sekvence, kterou bychom jako spr&#xE1;vn&#xED; program&#xE1;to&#x159;i cht&#x11B;li\nvy&#x10D;lenit do samostatn&#xE9; funkce.\nPomoc&#xED; samotn&#xE9;ho <code>yield</code> to ale jde celkem t&#x11B;&#x17E;ko:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">dance_hands</span><span class=\"p\">():</span>\n    <span class=\"k\">yield</span> <span class=\"s1\">&apos;putting hands forward&apos;</span>\n    <span class=\"k\">yield</span> <span class=\"s1\">&apos;putting hands down&apos;</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">dance</span><span class=\"p\">():</span>\n    <span class=\"k\">for</span> <span class=\"n\">action</span> <span class=\"ow\">in</span> <span class=\"n\">dance_hands</span><span class=\"p\">():</span>\n        <span class=\"k\">yield</span> <span class=\"n\">action</span>\n    <span class=\"k\">yield</span> <span class=\"s1\">&apos;turning around&apos;</span>\n    <span class=\"k\">yield</span> <span class=\"s1\">&apos;jumping&apos;</span>\n    <span class=\"k\">for</span> <span class=\"n\">action</span> <span class=\"ow\">in</span> <span class=\"n\">dance_hands</span><span class=\"p\">():</span>\n        <span class=\"k\">yield</span> <span class=\"n\">action</span>\n\n<span class=\"k\">for</span> <span class=\"n\">action</span> <span class=\"ow\">in</span> <span class=\"n\">dance</span><span class=\"p\">():</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">action</span><span class=\"p\">)</span>\n</pre></div><p>Tohle po&#x10D;tu &#x159;&#xE1;dk&#x16F; p&#x159;&#xED;li&#x161; nepomohlo. Existuje lep&#x161;&#xED; zp&#x16F;sob &#x2013; m&#xED;sto cyklu\nm&#x16F;&#x17E;eme pou&#x17E;&#xED;t <code>yield from</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">dance_hands</span><span class=\"p\">():</span>\n    <span class=\"k\">yield</span> <span class=\"s1\">&apos;putting hands forward&apos;</span>\n    <span class=\"k\">yield</span> <span class=\"s1\">&apos;putting hands down&apos;</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">dance</span><span class=\"p\">():</span>\n    <span class=\"k\">yield from</span> <span class=\"n\">dance_hands</span><span class=\"p\">()</span>\n    <span class=\"k\">yield</span> <span class=\"s1\">&apos;turning around&apos;</span>\n    <span class=\"k\">yield</span> <span class=\"s1\">&apos;jumping&apos;</span>\n    <span class=\"k\">yield from</span> <span class=\"n\">dance_hands</span><span class=\"p\">()</span>\n\n<span class=\"k\">for</span> <span class=\"n\">action</span> <span class=\"ow\">in</span> <span class=\"n\">dance</span><span class=\"p\">():</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">action</span><span class=\"p\">)</span>\n</pre></div><p>Nav&#xED;c lze <code>yield from</code> pou&#x17E;&#xED;t jako v&#xFD;raz, kter&#xFD; nab&#xFD;v&#xE1; hodnoty\nkterou podgener&#xE1;tor vr&#xE1;til (t.j. hodnota v&#xFD;jimky <code>StopIteration</code>).</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">fibonacci</span><span class=\"p\">(</span><span class=\"n\">limit</span><span class=\"p\">):</span>\n    <span class=\"n\">a</span> <span class=\"o\">=</span> <span class=\"mi\">0</span>\n    <span class=\"n\">b</span> <span class=\"o\">=</span> <span class=\"mi\">1</span>\n    <span class=\"n\">count</span> <span class=\"o\">=</span> <span class=\"mi\">0</span>\n    <span class=\"k\">while</span> <span class=\"n\">b</span> <span class=\"o\">&lt;</span> <span class=\"n\">limit</span><span class=\"p\">:</span>\n        <span class=\"k\">yield</span> <span class=\"n\">b</span>\n        <span class=\"n\">count</span> <span class=\"o\">+=</span> <span class=\"mi\">1</span>\n        <span class=\"n\">a</span><span class=\"p\">,</span> <span class=\"n\">b</span> <span class=\"o\">=</span> <span class=\"n\">b</span><span class=\"p\">,</span> <span class=\"n\">a</span> <span class=\"o\">+</span> <span class=\"n\">b</span>\n    <span class=\"k\">return</span> <span class=\"n\">count</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">fib_annotated</span><span class=\"p\">():</span>\n    <span class=\"n\">count</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"k\">yield from</span> <span class=\"n\">fibonacci</span><span class=\"p\">(</span><span class=\"mi\">10</span><span class=\"p\">))</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">&apos;LOG: Generated {} numbers&apos;</span><span class=\"o\">.</span><span class=\"n\">format</span><span class=\"p\">(</span><span class=\"n\">count</span><span class=\"p\">))</span>\n\n<span class=\"k\">for</span> <span class=\"n\">value</span> <span class=\"ow\">in</span> <span class=\"n\">fib_annotated</span><span class=\"p\">():</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">&apos;Got&apos;</span><span class=\"p\">,</span> <span class=\"n\">value</span><span class=\"p\">)</span>\n</pre></div><div class=\"highlight\"><pre><code>Got 1\nGot 1\nGot 2\nGot 3\nGot 5\nGot 8\nLOG: Generated 6 numbers</code></pre></div><p>Krom&#x11B; toho <code>yield from</code> deleguje hodnoty poslan&#xE9; pomoc&#xED; <code>send()</code> &#x10D;i <code>throw()</code>.</p>\n\n\n        "
    }
  }
}