{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"*Iterátory* jsou v Pythonu používané velice často, přestože o tom v mnoha případech ani nevíme nebo nepřemýšlíme. *Generátory* se staly jedním se základních kamenů pro Python 3.\n",
"\n",
"V této lekci:\n",
"* Se dozvíte co jsou *iterátory* a *generátory*.\n",
"* Naučíte se jak používat `for`.\n",
"* Dozvíte se že *kontejnery*, které už znáte, jsou *iterabilní*.\n",
"* Pochopítě užitečnost *generátorové notace*.\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Základní použití iterátorů a generátorů"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Nejprve se podíváme, jak se iterátory v Pythonu používají. Později se dozvíme, jak interně fungují. Jednoduše řečeno, iterátory slouží k postupnému procházení položek v nějakém objektu. Co jsou jednotlivé položky, závisí na daném objektu. Generátor je konkrétním typem iterátoru, který dynamicy vytváří (generuje) položky v závislosti na vnitřním stavu (typicky v závislosti na poslední iteraci). Položky tedy nejsou (resp. nemusí být) uloženy v paměti všechny najednou. \n",
"\n",
"Mnoho objektů v Pythonu je iterabilních (lze z nich vytvořit iterátor), zejména pak\n",
"\n",
"* kontejnery: `list`, `tuple`, `dict` apod.\n",
"* řetězce: `str`, `unicode`\n",
"* objekty typu stream, tedy např. `file`\n",
"\n",
"Poměrně přehledně to ukazuje článek [Iterables vs. Iterators vs. Generators](http://nvie.com/posts/iterators-vs-generators/), ze kterého je i tento názorný obrázek."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/plain": [
""
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from IPython.display import Image\n",
"Image(url='http://nvie.com/img/relationships.png', width=700)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `for` smyčky\n",
"`for` funguje v Pythonu výhradně na základě iterátorů. Neexistuje zde \"klasická\" for smyčka jako počítadlo. Syntaxi si ukážeme na příkladu procházení seznamu."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"x = 10\n",
"x = a\n",
"x = ('x', 0.001)\n"
]
}
],
"source": [
"# vytvoříme nějaký seznam\n",
"l = [10, \"a\", (\"x\", 1e-3)]\n",
"# projdeme položky pomocí for\n",
"for x in l:\n",
" print(f'x = {x}')\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Můžeme také procházet (iterovat) slovník - v takovém případě dostáváme postupně jednotlivé klíče. (Ale pozor: pořadí prvků slovníku je náhodné.)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"one -> 1\n",
"two -> 2\n"
]
}
],
"source": [
"d = {\"one\": 1, \"two\": 2}\n",
"for key in d:\n",
" print(f'{key} -> {d[key]}')\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pro procházení slovníku se hodí metoda `items`. Všimněte si použití dvou proměnných (ve skutečnosti to je `tuple`, jen je zde možná vynechat závorky) pro přiřazení, které slouží k *dekompozici*, stejně jako např. u volání funkcí."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"one -> 1\n",
"two -> 2\n"
]
}
],
"source": [
"d = {\"one\": 1, \"two\": 2}\n",
"for key, value in d.items():\n",
" print(f'{key} -> {value}')\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pokud chceme udělat klasické for počítadlo, musíme vytvořit objekt, který bude postupně vracet požadovaná čísla. K tomu slouží funkce `range`. *Použití počítadla by mělo být poslední volbou pro procházení číslovaných polí. Použijeme ho jen v případě, že opravdu potřebujeme čísla jako taková, nikoli pouze prvky nějakého seznamu.*"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Help on class range in module builtins:\n",
"\n",
"class range(object)\n",
" | range(stop) -> range object\n",
" | range(start, stop[, step]) -> range object\n",
" | \n",
" | Return an object that produces a sequence of integers from start (inclusive)\n",
" | to stop (exclusive) by step. range(i, j) produces i, i+1, i+2, ..., j-1.\n",
" | start defaults to 0, and stop is omitted! range(4) produces 0, 1, 2, 3.\n",
" | These are exactly the valid indices for a list of 4 elements.\n",
" | When step is given, it specifies the increment (or decrement).\n",
" | \n",
" | Methods defined here:\n",
" | \n",
" | __bool__(self, /)\n",
" | True if self else False\n",
" | \n",
" | __contains__(self, key, /)\n",
" | Return key in self.\n",
" | \n",
" | __eq__(self, value, /)\n",
" | Return self==value.\n",
" | \n",
" | __ge__(self, value, /)\n",
" | Return self>=value.\n",
" | \n",
" | __getattribute__(self, name, /)\n",
" | Return getattr(self, name).\n",
" | \n",
" | __getitem__(self, key, /)\n",
" | Return self[key].\n",
" | \n",
" | __gt__(self, value, /)\n",
" | Return self>value.\n",
" | \n",
" | __hash__(self, /)\n",
" | Return hash(self).\n",
" | \n",
" | __iter__(self, /)\n",
" | Implement iter(self).\n",
" | \n",
" | __le__(self, value, /)\n",
" | Return self<=value.\n",
" | \n",
" | __len__(self, /)\n",
" | Return len(self).\n",
" | \n",
" | __lt__(self, value, /)\n",
" | Return self integer -- return number of occurrences of value\n",
" | \n",
" | index(...)\n",
" | rangeobject.index(value) -> integer -- return index of value.\n",
" | Raise ValueError if the value is not present.\n",
" | \n",
" | ----------------------------------------------------------------------\n",
" | Static methods defined here:\n",
" | \n",
" | __new__(*args, **kwargs) from builtins.type\n",
" | Create and return a new object. See help(type) for accurate signature.\n",
" | \n",
" | ----------------------------------------------------------------------\n",
" | Data descriptors defined here:\n",
" | \n",
" | start\n",
" | \n",
" | step\n",
" | \n",
" | stop\n",
"\n"
]
}
],
"source": [
"help(range)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"range(0, 5)"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"range(5)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n",
"1\n",
"2\n",
"3\n",
"4\n"
]
}
],
"source": [
"for x in range(5):\n",
" print(x)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pokud už číslovaní potřebujeme, obvykle potřebujeme také hodnoty s daným indexem. V takovém případě použijeme `enumerate`:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0. egg\n",
"1. bacon\n",
"2. sausage\n",
"3. spam\n"
]
}
],
"source": [
"for i, x in enumerate(('egg', 'bacon', 'sausage', 'spam')):\n",
" print(f'{i}. {x}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Cvičení\n",
"\n",
"Pomocí `for` a `enumerate` naprogramujte funkci `find(where, what)`, která vrací pole indexů prvků `what` v iterabilním objektu `where`. Ověřte pomocí `assert` testů."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"def find(where, what):\n",
" ???\n",
" return indexes"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pokud `assert` nevyhodí výjimku, funkce je napsaná (velice pravděpodobně) správně."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert list(find(['FJFI', 2018, 2019], 2018)) == [1]\n",
"assert list(find((), 'whatever')) == []\n",
"assert list(find('Mississippi', 's')) == [2, 3, 5, 6]\n",
"print('Funkce find pracuje správně')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Generátorová notace\n",
"Pomocí generátorové notace se dají v Pythonu dělat (téměř) zázraky. Používá se pro zkrácený zápis tvorby nového objektu typu `list`, `dict`, `set` nebo generátoru pomocí závorek a klíčových slov `for`, `in`, případně `if`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Generátorový výraz (Generator expression)\n",
"Kulaté závorky: `(výraz for proměnná in iterable)`"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
" at 0x10655d660>"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(x ** 2 for x in range(1, 11)) # generátor, tj. iterovatelný objekt"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Generátory seznamů (List comprehension)\n",
"Hranaté závorky: `[výraz for proměnná in iterable]`"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[x ** 2 for x in range(1, 11)] # klasický seznam"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Toto můžeme použít i na převod generátoru na seznam."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# vytvoříme generátor\n",
"gen = (x ** 2 for x in range(1, 11))\n",
"# a teprve z něj vytvoříme list\n",
"[x for x in gen]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Generátory množin (Set comprehension)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Složené závorky: `{výraz for proměnná in iterable}`"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Množina počátečních písmen jmen (každé jen jednou):\n",
"{'R', 'E', 'M'}\n"
]
}
],
"source": [
"jmena = [\"Ester\", \"Eva\", \"Egon\", \"Marie\", \"Monika\", \"Richard\"]\n",
"prvni_pismena = {jmeno[0] for jmeno in jmena}\n",
"print(f\"Množina počátečních písmen jmen (každé jen jednou):\\n{prvni_pismena}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Generátory slovníků (Dictionary comprehension)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"(Podporováno od Pythonu 2.7.)\n",
"\n",
"Složené závorky: `{klíč: hodnota for proměnná in iterable}`. Od generátoru množit se liší přítomností dvojtečky ve výrazu klíč: hodnota."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"{'Ester': 5, 'Eva': 3, 'Egon': 4, 'Marie': 5, 'Monika': 6, 'Richard': 7}"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"jmena = [\"Ester\", \"Eva\", \"Egon\", \"Marie\", \"Monika\", \"Richard\"]\n",
"# Slovník s délkami jmen\n",
"{jmeno : len(jmeno) for jmeno in jmena}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Filtrování pomocí `if`\n",
"U generátorové notace můžeme použít `if` jako filtr. Jako příklad vybereme z mocnin dvou jen ty dvouciferné."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"[16, 32, 64]"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# vytvoříme generátor (horní mez jsme nějak odhadli)\n",
"gen = (2 ** n for n in range(1, 11))\n",
"[x for x in gen if 9 < x < 100]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Vícenásobné for\n",
"V jednom generátorovém výrazu můžeme mít více for klíčových slov. Bude se pak iterovat postupně přes všechny prvky všech iterárátorů za for. Je to ekvivalentní vnořenému for cyklu."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"{('a', 'a'),\n",
" ('a', 'c'),\n",
" ('a', 'e'),\n",
" ('b', 'a'),\n",
" ('b', 'c'),\n",
" ('b', 'e'),\n",
" ('c', 'a'),\n",
" ('c', 'c'),\n",
" ('c', 'e')}"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# vytvoření dvojic ze dvou množin\n",
"m1 = {\"a\", \"b\", \"c\"}\n",
"m2 = {\"a\", \"c\", \"e\"}\n",
"{(x1, x2) for x1 in m1 for x2 in m2}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Cvičení\n",
"\n",
"Přepište funkci `find` pomocí jednoho genráorového výrazu."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Užitečné funkce a metody\n",
"\n",
"### `zip`\n",
"K procházení více iterovatelných objektů nám zlouží `zip`. Tato funkce spáruje prvky n objektů do n-tic, takže jdou pak procházet současně, bez použití indexování."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1 -> 2\n",
"2 -> 4\n",
"3 -> 8\n",
"4 -> 16\n",
"5 -> 32\n",
"6 -> 64\n",
"7 -> 128\n",
"8 -> 256\n"
]
}
],
"source": [
"# vytvoříme seznamy a generátor\n",
"l1 = range(1,9)\n",
"l2 = (2 ** n for n in l1)\n",
"# nyní je chceme procházet současně\n",
"for x, y in zip(l1, l2):\n",
" print(f\"{x} -> {y}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `enumerate`\n",
"Pokud potřebujeme znát číselný index prvku, je lepší použít `enumerate`, která postupně vrací (index, prvek)."
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0 -> A\n",
"1 -> B\n",
"2 -> C\n",
"3 -> D\n",
"4 -> E\n"
]
}
],
"source": [
"for i, n in enumerate(\"ABCDE\"):\n",
" print(f\"{i} -> {n}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `dict.items`\n",
"Některé třídy mají pomocné metody pro iterace. Např. `dict.items` vrací dvojice (klíč, hodnota)."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"40 -> (\n",
"41 -> )\n",
"42 -> *\n",
"43 -> +\n",
"44 -> ,\n",
"45 -> -\n",
"46 -> .\n",
"47 -> /\n",
"48 -> 0\n",
"49 -> 1\n"
]
}
],
"source": [
"# slovník s částí ascii tabulky\n",
"d = {i: chr(i) for i in range(40, 50)}\n",
"for k, v in d.items():\n",
" print(f\"{k} -> {v}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### modul `itertools`\n",
"Tento modul obsahuje mnoho zajímavých a užitečných funkcí pro tvorbu iterátorů, často inspirovných funkcionálními jazyky (o funkcionálním programování v Pythonu ještě uslyšíme)."
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"['accumulate',\n",
" 'chain',\n",
" 'combinations',\n",
" 'combinations_with_replacement',\n",
" 'compress',\n",
" 'count',\n",
" 'cycle',\n",
" 'dropwhile',\n",
" 'filterfalse',\n",
" 'groupby',\n",
" 'islice',\n",
" 'permutations',\n",
" 'product',\n",
" 'repeat',\n",
" 'starmap',\n",
" 'takewhile',\n",
" 'tee',\n",
" 'zip_longest']"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# vypíšeme si funkce v itertools\n",
"import itertools\n",
"sorted([f for f in itertools.__dict__ if not f.startswith(\"_\")])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"V dokumentaci naleznete také recepty, jak vytvořit další užitečné funkce pomocí `itertools`. Ukažme si například, jak získat n-tý prvek z iterátoru (který pochopitelně nemusí mít číselné indexování)."
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3\n"
]
}
],
"source": [
"from itertools import islice\n",
"# definice funkce\n",
"def nth(iterable, n, default=None):\n",
" \"Returns the nth item or a default value\"\n",
" # všimněte si použití funkce next\n",
" return next(islice(iterable, n, None), default)\n",
"\n",
"# jednoduché použití\n",
"print(nth(range(100), 3))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Cvičení\n",
"\n",
"1. Pro slovníkovou reprezentaci polynomů implementujte funkci pro násobení polynomů `polydot(polynom1, polynom2)`. Slovníková reprezentace polynomu má jako klíče exponenty a jako hodnoty koeficienty u daných polynomů. Např. reprezentace $x^3 - 2x + 1$ je `{3: 1, 1:-2, 0: 1}`. Využijte `dict` metodu `get` nebo `collections.defaultdict`.\n",
"1. Naprogramujte ekvivalent funkce enumerate pomocí funkce zip (případně itertools.izip) a vhodné funkce z modulu `itertools`, která bude sloužit jako počítadlo. Aplikujte na libovolně vytvořený seznam jmen a vytvořte slovník, který přiřazuje pořadová čísla.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Test pro cvičení 1\n",
"\n",
"poly1 = {3: 1, 1: -2, 0: 1}\n",
"poly2 = {1: -1, 0: 2}\n",
"\n",
"assert polydot(poly1, poly2) == {4: -1, 3: 2, 2: 2, 1: -5, 0: 2}\n",
"print('Funkce polydot pracuje správně')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Architektura iterátorů\n",
"Účelem iterátorů je postupně procházet (iterovat) prvky objektu. To, jakým způsobem jsou chápány a implementovány prvky je specifické pro daný objekt. Klíčem k pochopení iterátorů jsou dvě speciálně pojmenované metody `__iter__` a `__next__` (`next` v Pythonu 2). Ty se obvykle nevolají přímo, ale např. pomocí `for` cyklu nebo generátorové notace. Ukažme si to na příkladu jednoduchého počítadla."
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n",
"1\n",
"2\n",
"3\n",
"[0, 1, 2, 3]\n"
]
}
],
"source": [
"class Counter(object):\n",
" \"\"\"Primitivní počítadlo\"\"\"\n",
"\n",
" def __init__(self, n):\n",
" self.n = n\n",
"\n",
" def __iter__(self):\n",
" self.i = 0\n",
" return self\n",
"\n",
" def __next__(self):\n",
" i = self.i\n",
" self.i += 1\n",
" if self.i > self.n:\n",
" raise StopIteration\n",
" return i\n",
"\n",
"# použijeme iterátor Counter ve for smyčce\n",
"my_counter = Counter(4)\n",
"\n",
"for a in my_counter:\n",
" print(a)\n",
"\n",
"# vytvoříme list pomocí generátorové notace\n",
"print([a for a in my_counter])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Jak to celé funguje? Pomocí iterátorového protokolu, který si můžeme ukázat krok za krokem. Jako první se vytvoří objekt pomocí metody `__iter__` (ta se zavolá při for cyklu i generátorové notaci)"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<__main__.Counter object at 0x1065b3130>\n",
"['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'i', 'n']\n"
]
}
],
"source": [
"# iter zavolá __iter__\n",
"it = iter(Counter(5))\n",
"print(it)\n",
"print(dir(it))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Poté se volá metoda `next` (resp. `__next__` v Python 3). Obojí je možné volat pomocí vestavěné funkce `next`:"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n",
"1\n"
]
}
],
"source": [
"print(next(it))\n",
"print(next(it))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Iterace končí vyhozením výjimky `StopIteration`. Funguje to tedy asi takto:"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n",
"1\n",
"2\n",
"3\n"
]
}
],
"source": [
"it = iter(Counter(4))\n",
"while True:\n",
" try:\n",
" print(next(it))\n",
" except StopIteration:\n",
" break"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"No a teď už konečně víme, jak funguje \"klasický\" `for` cyklus s počítáním pomocí funkce `range`, resp. `xrange`. (V Pythonu 2 vrací `range` list a `xrange` generátor. V Pythonu 3 existuje už jen `range`, který vrací generátor.)"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n",
"1\n",
"2\n",
"3\n"
]
}
],
"source": [
"for i in range(4):\n",
" print(i)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"V Pythonu jsou iterace základním (a vlastně jediným) mechanismem `for` smyček. Pokud chceme provést nějakou operaci na množině objektů, která je typicky uložena v nějakém kontejneru (`list`, `tuple`, `dic`, `set` spod.), použijeme k tomu iterace. Ukážeme si několik příkladů."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Architektura generátorů\n",
"\n",
"Pro vytvoření generátoru, nebo lépe řečené *generátorové funkce*, služí klíčové slovo `yield`. Jakmile se při běhu funkce narazí na klíčové slovo `yield`, funkce se \"zmrazí\" (zachová se interní stav pomocí uzávěru nebo closure -- o tom si ještě povíme) a vrátí se výraz za `yield`. Spuštění generátorová funkce jako takové pak vrací iterátor, který řídí spouštění této funkce. Ukážeme si jednoduchý příklad, který počítá donekonečna."
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"def countup(value=0):\n",
" print(\"Příkazy se spustí až po zavolání prvního next\")\n",
" while True:\n",
" yield value\n",
" value += 1"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [],
"source": [
"g = countup(2)"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Příkazy se spustí až po zavolání prvního next\n"
]
},
{
"data": {
"text/plain": [
"2"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(g)"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"3"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(g)"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"4"
]
},
"execution_count": 36,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(g)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Takto bychom mohli pokračovat donekonečna. Pokud chceme, aby iterátor někde zastavil, musíme použít vyjímku."
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"def countupto(to_value, from_value=0):\n",
" value = from_value\n",
" while value < to_value:\n",
" yield value\n",
" value += 1\n",
" # tuto výjimky můžeme vyhodit manuálně, v tomto případě to ale není nutné\n",
" # raise StopIteration"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {},
"outputs": [],
"source": [
"g = countupto(2)"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(g)"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"next(g)"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"ename": "StopIteration",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mStopIteration\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[41], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;43mnext\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mg\u001b[49m\u001b[43m)\u001b[49m\n",
"\u001b[0;31mStopIteration\u001b[0m: "
]
}
],
"source": [
"next(g)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Výjimka `StopIteration` je odchycena ve for cyklech, generátorech seznamů apod. Můžeme tedy udělat např. toto:"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"data": {
"text/plain": [
"[1, 2, 3, 4, 5, 6, 7, 8, 9]"
]
},
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[i for i in countupto(10, 1)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pokud toto zkusíte s `countup`, iterace nikdy neskončí, resp. skončí nějakou chybou nebo přehřátím počítače.\n",
"\n",
"Ukázali jsme si základní tvorbu generátorů. Celý protokol je ale bohatší a umožňuje komunikovat s generátorovou funkcí pomocí posílání hodnot nebo výjimek, viz [dokumentace](http://docs.python.org/2/reference/expressions.html#yieldexpr)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Cvičení\n",
"\n",
"1. Vytvořte generátorovou funkci pro čísla, která jsou definována rekurentním vztahem $$F_{n}=F_{n-1}+F_{n-2},\\ F_0 = 0,\\ F_1 = 1$$"
]
}
],
"metadata": {
"anaconda-cloud": {},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.16"
}
},
"nbformat": 4,
"nbformat_minor": 4
}