{ "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 }