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.
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.
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
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.
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
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
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átory</h1>\n<p>Dnes si popíšeme, jak v Pythonu fungují <em>generátory</em>, tedy funkce s příkazem <code>yield</code>.\nNěkteří z vás možná už nějaký jednoduchý generátor napsali, ale pojďme si je\nvysvětlit od úplného začátku: od toho, jak se v Pythonu iteruje.</p>\n<h2>Iterace</h2>\n<p>Když je v Pythonu potřeba iterovat (např. příkazem <code>for</code>) přes nějakou kolekci\n(seznam, řetězec, soubor, …), použije se <em>iterační protokol</em>,\nkterý pracuje se dvěma druhy objektů: s <em>iterovatelnými objekty</em> a s <em>iterátory</em>.</p>\n<p>Iterovatelné objekty (<em>iterables</em>) se vyznačují tím, že je na ně možné zavolat\nfunkci <code>iter()</code>. Ta vrátí příslušný iterátor:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">>>> </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\"><list_iterator object at 0x...></span>\n</pre></div><p>Na iterátor pak je možné opakovaně volat funkci <code>next()</code>, čímž dostáváme jednotlivé\nprvky iterace.\nPo vyčerpání iterátoru způsobuje <code>next()</code> výjimku <code>StopIteration</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">>>> </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\">>>> </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\">>>> </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\">>>> </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\">>>> </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\">>>> </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ělá ně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ělo původního cyklu</span>\n</pre></div><p>Platí, že každý iterátor je iterovatelný: zavoláním <code>iter()</code> na iterátor\ndostaneme ten stejný iterátor (nikoli jeho kopii) zpět.\nNaopak to ale obecně neplatí: např. seznamy jsou iterovatelné, ale nejsou samy\no sobě iterátory.\nKaždé zavolání <code>iter(seznam)</code> vrací nový iterátor, který má svou vlastní\n„aktuální pozici“ a iteruje od začátku.</p>\n<p>Iterátor je ve většině případů „malý“ objekt, který si „pamatuje“ jen původní iterovatelný\nobjekt a aktuální pozici. Příklady jsou iterátory seznamů (<code>iter([])</code>), slovníků (<code>iter({})</code>),\n<em>n</em>-tic nebo množin, iterátor pro <code>range</code> a podobně.</p>\n<p>Iterátory ale můžou být i „větší“: třeba otevřený soubor je iterátor, z něhož <code>next()</code>\nnačítá jednotlivé řádky.</p>\n<h2>Generátory</h2>\n<p>Asi nejzajímavější druh iterátoru je tzv. <em>generátor</em>: funkce, která umí postupně\ndávat k dispozici hodnoty.\nDefinuje se pomocí klíčového slova <code>yield</code>: každá funkce, která obsahuje <code>yield</code>,\nje <em>generátorová 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\">"""generates 2 numbers"""</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">'A'</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\">'B'</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\">'C'</span><span class=\"p\">)</span>\n</pre></div><p>Zavoláním takové funkce dostáváme <em>generátorový iterátor</em> (angl. <em>generator iterator</em>):</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">>>> </span><span class=\"n\">generate2</span><span class=\"p\">()</span>\n<span class=\"go\"><generator object generate2 at 0x...></span>\n</pre></div><p>Voláním <code>next()</code> se pak stane zajímavá věc: funkce se provede až po první <code>yield</code>,\ntam se <em>zastaví</em> a hodnota <code>yield</code>-u se vrátí z <code>next()</code>.\nPři dalším volání se začne provádět zbytek funkce od místa, kde byla naposled\nzastavena.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">>>> </span><span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"n\">generate2</span><span class=\"p\">()</span>\n<span class=\"gp\">>>> </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\">>>> </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\">>>> </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ší použití generátorů</h2>\n<p>Vlastnost přerušit provádění funkce je velice užitečná nejen pro vytváření\nsekvencí, ale má celou řadu dalších užití.\nExistuje třeba dekorátor, který generátorovou funkci s jedním <code>yield</code> převede na <em>context manager</em>,\ntedy objekt použitelný s pří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\">'Entering'</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\">'Exiting'</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\">'Inside context, with'</span><span class=\"p\">,</span> <span class=\"n\">obj</span><span class=\"p\">)</span>\n</pre></div><p>Vše před <code>yield</code> se provede při vstupu do kontextu, hodnota <code>yield</code> se předá\ndál a vše po <code>yield</code> se provede na konci.\nMůžeme si představit, že místo <code>yield</code> se „doplní“ obsah bloku <code>with</code>.\nFunkce se tam na chvíli zastaví a může se tedy provádět něco jiného.</p>\n<h2>Vracení hodnot z generátorů</h2>\n<p>V rámci generátorové funkce můžeme použít i <code>return</code>, který funkci ukončí.\nVrácená hodnota se však při normální iteraci (např. ve <code>for</code>) nepoužije.\nObjeví se pouze jako hodnota výjimky <code>StopIteration</code>, která 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\">"""Yield two numbers and return their sum"""</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\">>>> </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\">>>> </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\">>>> </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\">>>> </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\">"<stdin>"</span>, line <span class=\"m\">1</span>, in <span class=\"n\"><module></span>\n<span class=\"gr\">StopIteration</span>: <span class=\"n\">5</span>\n</pre></div><h2>Obousměrná komunikace</h2>\n<p>Oproti normálním iterátorům, které hodnoty jen poskytují, mají generátory metodu\n<code>send()</code>, kterou je možné posílat hodnoty <em>do</em> běžícího generátoru.\nKlíčové slovo <code>yield</code> totiž může fungovat jako výraz a tento výraz nabývá poslanou\nhodnotu (nebo <code>None</code>, byl-li použit normální <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í iteraci nelze použít send() -- nečekáme zatí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římě řečeno, metoda <code>send()</code> není příliš užitečná.\n(Když byste něco takového potřebovali, radši si napište třídu, která si bude\nstav uchovávat v atributech, a měňte ji třeba metodami. Bude to pravděpodobně\npřehlednější.)\nExistuje ale příbuzná metoda, která už je užitečnější: <code>throw()</code>.\nTa do generátoru „vhodí“ výjimku.\nZ pohledu generátorové funkce to vypadá, jako by výjimka nastala na pří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\">'Death by'</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\">>>> </span><span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"n\">report_exception</span><span class=\"p\">()</span>\n<span class=\"gp\">>>> </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span> <span class=\"c1\"># opět – v první iteraci nelze throw() použít</span>\n<span class=\"gp\">>>> </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\">>>> </span><span class=\"n\">value</span>\n<span class=\"go\">123</span>\n</pre></div><p>Podobná věc se děje, když generátorový iterátor zanikne: Python do generátoru\n„vhodí“ výjimku GeneratorExit.\nTa dědí z <code>BaseException</code>, ale ne <code>Exception</code>, takže klasické <code>except Exception:</code>\nji nechytí (ale např. <code>finally</code> funguje jak má).\nPokud generátor tuto výjimku chytá, měl by se co nejdřív ukončit.\n(Když to neudělá a provede další <code>yield</code>, Python ho ukončí „násilně“.)</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">>>> </span><span class=\"kn\">import</span> <span class=\"nn\">gc</span>\n<span class=\"gp\">>>> </span><span class=\"n\">it</span> <span class=\"o\">=</span> <span class=\"n\">report_exception</span><span class=\"p\">()</span>\n<span class=\"gp\">>>> </span><span class=\"nb\">next</span><span class=\"p\">(</span><span class=\"n\">it</span><span class=\"p\">)</span>\n<span class=\"gp\">>>> </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íme se objektu "it"</span>\n<span class=\"go\">Death by GeneratorExit</span>\n<span class=\"go\">Exception ignored in: <generator object report_exception at 0x...></span>\n<span class=\"go\">RuntimeError: generator ignored GeneratorExit</span>\n<span class=\"go\">0</span>\n</pre></div><h2>Delegace na podgenerátor – <code>yield from</code></h2>\n<p>Máme následující generá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\">'putting hands forward'</span>\n <span class=\"k\">yield</span> <span class=\"s1\">'putting hands down'</span>\n <span class=\"k\">yield</span> <span class=\"s1\">'turning around'</span>\n <span class=\"k\">yield</span> <span class=\"s1\">'jumping'</span>\n <span class=\"k\">yield</span> <span class=\"s1\">'putting hands forward'</span>\n <span class=\"k\">yield</span> <span class=\"s1\">'putting hands down'</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ěm jistá sekvence, kterou bychom jako správní programátoři chtěli\nvyčlenit do samostatné funkce.\nPomocí samotného <code>yield</code> to ale jde celkem těž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\">'putting hands forward'</span>\n <span class=\"k\">yield</span> <span class=\"s1\">'putting hands down'</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\">'turning around'</span>\n <span class=\"k\">yield</span> <span class=\"s1\">'jumping'</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čtu řádků příliš nepomohlo. Existuje lepší způsob – místo cyklu\nmůžeme použí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\">'putting hands forward'</span>\n <span class=\"k\">yield</span> <span class=\"s1\">'putting hands down'</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\">'turning around'</span>\n <span class=\"k\">yield</span> <span class=\"s1\">'jumping'</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íc lze <code>yield from</code> použít jako výraz, který nabývá hodnoty\nkterou podgenerátor vrátil (t.j. hodnota vý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\"><</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\">'LOG: Generated {} numbers'</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\">'Got'</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ě toho <code>yield from</code> deleguje hodnoty poslané pomocí <code>send()</code> či <code>throw()</code>.</p>\n\n\n " } } }