{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "748ad676",
   "metadata": {},
   "source": [
    "# Генераторы #"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81ac59b4",
   "metadata": {},
   "source": [
    "На этой лекции мы с вами обсудим генераторы, которые являются очень важным механизмом, на котором построено многое в Python'е.\n",
    "\n",
    "Итак, простейший генератор — это функция, в которой есть оператор `yield`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "20b39e22",
   "metadata": {},
   "outputs": [],
   "source": [
    "def even_range(start, end):\n",
    "    current = start\n",
    "    while current < end:\n",
    "        yield current\n",
    "        current += 2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8dd3b8b1",
   "metadata": {},
   "source": [
    "Что же делает наш генератор? У нас генератор — это `even_range`, который работает по упрощенной схеме знакомого вам генератора `range`. Он принимает `(start, end)`, записывает текущий `start`, и пока у нас `current` меньше конечного значения, он `yield`'ит `current` и прибавляет двойку."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c4a176d4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n",
      "2\n",
      "4\n",
      "6\n",
      "8\n"
     ]
    }
   ],
   "source": [
    "for number in even_range(0, 10):\n",
    "    print(number)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "94a8da7f",
   "metadata": {},
   "source": [
    "Что же делает этот оператор `yield`? `Yield` можно рассматривать как какой-то временный `return`, то есть у нас возвращается значение `current`. Однако выполнение функции не прекращается, это не обычный `return`, `return`, как вы видите, здесь вот в конце, у нас `return None` по умолчанию. `Yield` возвращает значение, но прерывает выполнение функции только на время, то есть мы можем вернуться к этой функции, к этому моменту.\n",
    "\n",
    "Важно знать, что мы можем итерироваться, например, по генератору, можем пробежаться и вывести все значения."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2e7ddcd",
   "metadata": {},
   "source": [
    "Каждый раз, когда у нас выполняется `yield`, у нас возвращается значение `current`, и каждый раз, когда мы просим следующий элемент, у нас выполнение функции возвращается в этот же момент, и мы идем дальше. Чтобы посмотреть, как это происходит на самом деле, можно воспользоваться функцией next, которая действительно применяется каждый раз при итерации. Таким образом, мы создаем наш итератор, наш генератор, и вызываем `next`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "f652e038",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ranger = even_range(0, 4)\n",
    "next(ranger)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "78b9cbcb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "next(ranger)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "30534428",
   "metadata": {},
   "outputs": [
    {
     "ename": "StopIteration",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m                             Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-5-c186e1cb4ee7>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mnext\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mranger\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m: "
     ]
    }
   ],
   "source": [
    "next(ranger)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d8c18d1",
   "metadata": {},
   "source": [
    "В начале у нас получается 0. Потом вызывается `next`, получается 2. И когда у нас наш генератор исчерпан, когда у нас условие, что текущее значение меньше 4 не выполняется, — у нас вызывается `StopIteration`, выбрасывается исключение и больше `yield`'ов нет, у нас происходит `return`.\n",
    "\n",
    "Именно так работают генераторы. Чтобы понять, что действительно происходит, можно, например, добавить сюда `print` после `yield`'а."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "c7a44318",
   "metadata": {},
   "outputs": [],
   "source": [
    "def list_generator(list_obj):\n",
    "    for item in list_obj:\n",
    "        yield item\n",
    "        print(f\"After yielding {item}\")\n",
    "        \n",
    "generator = list_generator([1, 2])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ee7ee21",
   "metadata": {},
   "source": [
    "Давайте посмотрим, что происходит при вызове `next`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "441b6575",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "next(generator)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b405a97b",
   "metadata": {},
   "source": [
    "У нас есть наш `list_generator`, он принимает `list` какой-то `object` и просто `yield`'ит все элементы по порядку. Мы вызываем `next(generator)`, то есть берем следующий элемент из генератора, у нас возвращается единичка, первый элемент списка. Логично. Однако у нас почему-то не вывелось `print`. Не вывелось именно потому, что у нас выполнение функции генератора прервано. На этом моменте у нас вернулся элемент, и у нас запомнилось состояние функции в какой-то этот момент. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "870d8e66",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "After yielding 1\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "next(generator)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64be79b0",
   "metadata": {},
   "source": [
    "И выводится `After yielding` у нас выводится, когда мы берем следующий элемент, то есть у нас выполнение продолжается с вот этого момента и идет до тех пор, пока нет следующего `yield`'а. Таким образом мы доходим до двойки и все."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "420efed7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "After yielding 2\n"
     ]
    },
    {
     "ename": "StopIteration",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m                             Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-12-323ce5d717bb>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mnext\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mgenerator\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m: "
     ]
    }
   ],
   "source": [
    "next(generator)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cab954f3",
   "metadata": {},
   "source": [
    "Собственно, чем полезны эти генераторы? Казалось бы, концепт понятный, но не понятно, зачем их использовать. Оказывается, очень важной бывает именно вот эта вот особенность, которая позволяет хранить состояние функции и возвращаться к этому состоянию раз за разом. Таким образом, например, реализована функция `range`. Функция `range`, как вы знаете, позволяет вам получить генератор, какой-то итерабельный объект, по которому мы можем пробежаться и, например, выводить просто в цикле числа или делать что-нибудь более сложное. Функция `range` позволяет вам не загружать в память сразу огромный список чисел. Таким образом раньше в Python'е, например, была функция `range` безгенераторная и `xrange` генераторная. Функция `range` загружала в память огромное количество чисел, чтобы потом по ним итерироваться. Генератор позволяет вам делать очень просто. Он позволяет запомнить текущее положение и от него уже идти дальше."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f534d54f",
   "metadata": {},
   "source": [
    "Классический пример — это числа Фибоначчи."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "ff283572",
   "metadata": {},
   "outputs": [],
   "source": [
    "def fibonacci(number):\n",
    "    a = b = 1\n",
    "    \n",
    "    for _ in range(number):\n",
    "        yield a\n",
    "        a, b = b, a + b"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4edb25a",
   "metadata": {},
   "source": [
    "Нам нужно получить числа Фибоначчи до какого-то момента. Опять же, обратите внимание, нам не нужно запоминать огромное количество чисел Фибоначчи, которые очень быстро растут. Нам нужно здесь только помнить два конкретных числа и возвращаться в момент, когда мы остановились."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "ca0e7f6b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "1\n",
      "2\n",
      "3\n",
      "5\n",
      "8\n",
      "13\n",
      "21\n",
      "34\n",
      "55\n"
     ]
    }
   ],
   "source": [
    "for num in fibonacci(10):\n",
    "    print(num)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8af9fb69",
   "metadata": {},
   "source": [
    "Итак, очень важная особенность генераторов, что они позволяют хранить состояние и возвращаться к нему и использовать это для того, чтобы оптимизировать работу с памятью. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9054bfd",
   "metadata": {},
   "source": [
    "Еще один очень важный момент — это возможность генераторам получать какие-то значения. Мы можем не только возвращаться к моменту, когда у нас генератор сделал `yield`, и продолжать исполнение с этого момента с каким-то контекстом, мы можем еще и передавать туда значения. Этот момент используется очень активно в асинхронном программировании, о котором мы с вами поговорим чуть позже."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f33f1bd3",
   "metadata": {},
   "source": [
    "А пока давайте определим `accumulator`, нашу генераторную функцию, которая хранит какое-то общее количество данных и просто в бесконечном цикле получает с помощью оператора `yield` значение. Давайте посмотрим, что здесь происходит в простейшем виде."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "97d62f6d",
   "metadata": {},
   "outputs": [],
   "source": [
    "def accumulator():\n",
    "    total = 0\n",
    "    while True:\n",
    "        value = yield total\n",
    "        print(f\"Got: {value}\")\n",
    "        \n",
    "        if not value: break\n",
    "            \n",
    "        total += value\n",
    "    \n",
    "generator = accumulator()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "161da2c5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "next(generator)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55ec02a9",
   "metadata": {},
   "source": [
    "Вначале мы инициализируем наш генератор, то есть мы его стартуем. У нас возвращается `yield total`, которая равна нулю по умолчанию в начале цикла. Дальше, мы можем послать данные в генератор с помощью метода генератора `send`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "e359097d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Got: 1\n",
      "Accumulated: 1\n"
     ]
    }
   ],
   "source": [
    "print(f\"Accumulated: {generator.send(1)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c6c8b6c3",
   "metadata": {},
   "source": [
    "Что же здесь мы делаем? Мы передаем в наш генератор единичку, мы посылаем туда значение, то есть у нас есть какая-то точка исполнения, у нас остановилось исполнение здесь. Мы можем послать в эту точку значение, и это значение запишется в `value`. Таким образом мы передали в `value` единицу. Мы можем вывести, что мы действительно получили единицу.\n",
    "\n",
    "Дальше, если у нас в `value` не было передано, мы хотим выйти. У нас есть `value`, поэтому мы прибавляем к нашему `total`'у `value`, которому мы передали. Собственно, accumulator аккумулирует значение, что логично. Итак, у нас накопилась единичка.\n",
    "\n",
    "Дальше, мы можем послать еще одну единичку и окажется, что мы действительно получили единичку, а накопили уже два, потому что наша функция хранит состояние, и у нас хранится контекст, в котором у нас `total` уже равен двум."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "9d27bdf5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Got: 1\n",
      "Accumulated: 2\n"
     ]
    }
   ],
   "source": [
    "print(f\"Accumulated: {generator.send(1)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b4adf6b",
   "metadata": {},
   "source": [
    "Мы можем передать еще одну единичку, и у нас очевидно `Accumulated` станет равен трем."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "8972b01d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Got: 1\n",
      "Accumulated: 3\n"
     ]
    }
   ],
   "source": [
    "print(f\"Accumulated: {generator.send(1)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2e9d9826",
   "metadata": {},
   "source": [
    "Мы передали единичку, о чем и говорим. Если мы ничего не передадим, а просто пойдем дальше, то у нас по условию выхода из цикла, как вы помните, заканчивается наш генератор и выбрасывается исключение `StopIteration`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "13dace76",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Got: None\n"
     ]
    },
    {
     "ename": "StopIteration",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m                             Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-21-323ce5d717bb>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mnext\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mgenerator\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m: "
     ]
    }
   ],
   "source": [
    "next(generator)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e6b2588",
   "metadata": {},
   "source": [
    "Видите, мы ничего не получили, мы получили `None`, поэтому мы закончили выполнение.\n",
    " \n",
    "Итак, генераторы являются очень важной концепцией, которая используется во многих особенностях языка Python, например, в асинхронном программировании. Они позволяют вам создавать функции, к которым можно возвращаться, которые хранят контекст выполнения и позволяют вам оптимально работать с памятью."
   ]
  }
 ],
 "metadata": {
  "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.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
