{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "888a9140",
   "metadata": {},
   "source": [
    "# Генераторы и сопрограммы #"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fd782031",
   "metadata": {},
   "source": [
    "На предыдущей лекции мы рассмотрели то, как работают генераторы в Python. На этой лекции мы познакомимся с сопрограммами и выясним, в чем отличие и сходство между генераторами и сопрограммами в Python. Также мы обсудим, как работает конструкция `yield from` и рассмотрим примеры работы различных сопрограмм.\n",
    "\n",
    "Итак, давайте перейдем к примеру и рассмотрим, как выглядит сопрограмма."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "5ac63f59",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Сопрограммы (корутины)\n",
    "def grep(pattern):\n",
    "    print(\"start grep\")\n",
    "    while True:\n",
    "        line = yield\n",
    "        \n",
    "        if pattern in line:\n",
    "            print(line)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "ba7628fb",
   "metadata": {},
   "outputs": [],
   "source": [
    "g = grep(\"python\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "cb95ffaa",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "start grep\n"
     ]
    }
   ],
   "source": [
    "next(g)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "2ba2e231",
   "metadata": {},
   "outputs": [],
   "source": [
    "g.send(\"golang is better?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "ced3ecae",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "python is simple!\n"
     ]
    }
   ],
   "source": [
    "g.send(\"python is simple!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0bcba4fa",
   "metadata": {},
   "source": [
    "Предположим, наша цель — фильтровать входной поток при помощи функции `grep`. В функцию `grep` мы передаем некий паттерн, например, это пусть будет строчка `python`. Далее мы должны в эту функцию передать некие строчки и вывести на экран только те, в которых присутствует строка `python`. При помощи сопрограмм наша функция `grep` выглядит следующим образом.\n",
    "\n",
    "Мы в бесконечном цикле вызываем такую конструкцию - `line` присвоить `yield`. В этом месте очень важно уделить внимание отличию от того, как `yield` используется в генераторах. В генераторах мы выражение присваивали справа от конструкции `yield`, а здесь мы, наоборот, переменной присваиваем результат работы конструкции `yield`.\n",
    "\n",
    "Что произойдет в данном случае? В данном случае функция как бы заморозит свое значение и будет ожидать ввода данных при помощи метода `send`. Итак, если мы вызовем функцию `grep`, будет создана корутина. Для того чтобы запустить нашу корутину, необходимо вызвать метод `next`. После того, как метод `next` будет вызван, запустится код нашей функции, выведется строчка `start grep`, запустится бесконечный цикл, код дойдет до инструкции `yield`, и здесь вернется управление в основной поток.\n",
    "\n",
    "После этого в основном потоке мы отправляем данные нашей корутине, и код возобновляет свою работу дальше. Давайте посмотрим, как код будет выполняться в отладчике в консоли. Нам потребуется код нашего примера этой утилиты — это наша корутина `grep`. Запускаем отладчик при помощи команды python3, указываем флаг `−m pdb` и запускаем наш отладчик."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d2fa9a73",
   "metadata": {},
   "outputs": [],
   "source": [
    "! python -m pdb ex4.py"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5cb7ca6c",
   "metadata": {},
   "source": [
    "Давайте разбираться с тем, как будет выполняться наша корутина. Итак, мы объявили нашу функцию `grep`. Далее, мы создали объект типа генератор. Давайте еще раз убедимся, что это действительно генератор. Наш объект `g`, после того как ему присвоили выражение `grep(\"python\")`, создался, а вызова пока еще не произошло никакого для этой функции. Далее мы вызываем метод `next(g)`, и это будет равносильно тому, что мы бы вызвали метод `g.send` и туда передали значение `None` и эта конструкция запустит нашу корутину.\n",
    "\n",
    "Итак, мы провалились в нашу функцию, вывели в консоль строчку \"start grep\", зашли в наш цикл, дошли до инструкции `yield`, и здесь произошел возврат. Хочу обратить ваше внимание, как произошел этот возврат. Он не равносилен инструкции `return` для функции. Дело в том, что в объекте `g`, который является корутиной, запомнился указатель на фрейм стека, запомнились все локальные переменные. Функция помнит свое состояние. Она как бы заморозила свое выполнение в том месте, где была указана инструкция `yield`. Теперь наша корутина запущена, она помнит свое состояние, и она ожидает, пока кто-нибудь вызовет метод `send` и отправит в нее данные. Давайте продолжим выполнение. Итак, мы отправили данные при помощи вызова `send`. Мы опять вернулись в нашу функцию, но вернулись именно в то место, где завершили или заморозили свою работу. Это инструкция `yield`. Инструкций `yield` может быть несколько. Давайте посмотрим, чему равна переменная `line`. В данном случае наше условие для фильтрации не будет выполнено, и на экран ничего не напечатается. Давайте продолжим выполнение. Мы снова в цикле продолжаем работу этой нашей корутины, снова выполняем строчку `line` присвоить `yield`, возвращаемся из нашей корутины. В основном потоке мы опять отправляем данные в нашу корутину. В данном случае это будет строчка \"python is simple\". Опять проваливаемся в нашу функцию. Давайте посмотрим, чему равна переменная `line`. Да, действительно, это то, что мы в нее отправили при помощи метода `send`. В данном случае наша корутина напечатает на экран эту строчку. Все, я сейчас запустил все заново. На самом деле у функции `g` наша программа прекратила свою работу.\n",
    "\n",
    "Итак, давайте еще раз остановимся на ключевых моментах и отличии того, как работает корутина по сравнению с генератором. Здесь мы видим немного другое использование инструкции `yield`. Мы в переменную присваиваем ее значение. Внутри корутины она не производит значение, она потребляет значение, и точно так же приостанавливает или замораживает свое состояние, ждет, пока это значение в нее поступит. После поступления значения она возобновляет свою работу. Давайте продолжим изучать особенности корутин и посмотрим следующий пример."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "8a676c5e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Сопрограммы, вызов метода close()\n",
    "def grep(pattern):\n",
    "    print(\"start grep\")\n",
    "    try:\n",
    "        while True:\n",
    "            line = yield\n",
    "            if pattern in line:\n",
    "                print(line)\n",
    "                \n",
    "    except GeneratorExit:\n",
    "        print(\"stop grep\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "0b51a749",
   "metadata": {},
   "outputs": [],
   "source": [
    "g = grep(\"python\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "c5133a0a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "start grep\n"
     ]
    }
   ],
   "source": [
    "next(g)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "0a2d7d0a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "python is the best!\n"
     ]
    }
   ],
   "source": [
    "g.send(\"python is the best!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "7b508e1f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "stop grep\n"
     ]
    }
   ],
   "source": [
    "g.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fde51a2c",
   "metadata": {},
   "source": [
    "Иногда необходимо остановить запущенную корутину. Делается это при помощи вызова метода `close` для объекта корутины. Метод `close` будет вызван автоматически сборщиком мусора, но если нам нужно самим остановить корутину, то можно руками вызвать метод `close`. Метод `close` сгенерирует исключение генератора `exit` в том месте, где функция заморозила свое значение, свою работу.\n",
    "\n",
    "Это исключение нельзя игнорировать, его нужно обрабатывать и завершать свою работу. Обрабатывать его можно при помощи стандартной работы с исключениями, при помощи блока `try except`.\n",
    "\n",
    "Иногда необходимо передать исключения в саму корутину. Это делается при помощи вызова метода `throw`. Пример точно такой же."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "7f4a00f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Сопрограммы, генерация исключений\n",
    "def grep(pattern):\n",
    "    print(\"start grep\")\n",
    "    try:\n",
    "        while True:\n",
    "            line = yield\n",
    "            if pattern in line:\n",
    "                print(line)\n",
    "        \n",
    "    except GeneratorExit:\n",
    "        print(\"stop grep\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "84247ff8",
   "metadata": {},
   "outputs": [],
   "source": [
    "g = grep(\"python\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "05476865",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "start grep\n"
     ]
    }
   ],
   "source": [
    "next(g)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "17031b8a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "python is the best!\n"
     ]
    }
   ],
   "source": [
    "g.send(\"python is the best!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "bfabf7a3",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "ename": "RuntimeError",
     "evalue": "something wrong",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mRuntimeError\u001b[0m                              Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-15-bdcc1bc37767>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mg\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mthrow\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mRuntimeError\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"something wrong\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m<ipython-input-11-6c8a24661054>\u001b[0m in \u001b[0;36mgrep\u001b[0;34m(pattern)\u001b[0m\n\u001b[1;32m      4\u001b[0m     \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      5\u001b[0m         \u001b[0;32mwhile\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m             \u001b[0mline\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32myield\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      7\u001b[0m             \u001b[0;32mif\u001b[0m \u001b[0mpattern\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mline\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      8\u001b[0m                 \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mline\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mRuntimeError\u001b[0m: something wrong"
     ]
    }
   ],
   "source": [
    "g.throw(RuntimeError, \"something wrong\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "59273372",
   "metadata": {},
   "source": [
    "Наша корутина приостановила свою работу на вот этой инструкции, и если мы отправим в нее исключение, то оно будет сгенерировано именно в этом месте. Сгенерированное исключение можно обработать при помощи блока `try except`, в общем, стандартным способом для языка Python.\n",
    "\n",
    "Давайте посмотрим еще более сложный пример."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "20a857c8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Вызовы сопрограмм, PEP 380\n",
    "def grep(pattern):\n",
    "    print(\"start grep\")\n",
    "    while True:\n",
    "        line = yield\n",
    "        if pattern in line:\n",
    "            print(line)\n",
    "    \n",
    "def grep_python_coroutine():\n",
    "    g = grep(\"python\")\n",
    "    next(g)\n",
    "    g.send(\"python is the best!\")\n",
    "    g.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "3bb19540",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "start grep\n",
      "python is the best!\n"
     ]
    }
   ],
   "source": [
    "g = grep_python_coroutine() # is g coroutine?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "8938caa3",
   "metadata": {},
   "outputs": [],
   "source": [
    "g"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "75a1805d",
   "metadata": {},
   "source": [
    "У нас есть функция `grep`, У нас есть корутина `grep`, у нее внутри есть инструкция `yield`, и мы хотим вызвать нашу корутину `grep` в другой корутине. Как это сделать? \n",
    "\n",
    "Если мы напишем код, который приведен на слайде, и потом сделаем данный вызов, то будет ли переменная `g` являться корутиной? На самом деле, не будет. Так как она не содержит инструкции `yield`, она выполнится сразу, она будет являться обычной функцией. Для того чтобы было удобно вызывать из одних корутин другие, в Python разработали стандарт `PEP 0380` и в Python 3 его реализовали. И теперь в Python 3 появилась инструкция `yield from`. При помощи нее можно выполнить делегирование вызова одной корутины в другой. Наш пример можно переписать следующим образом."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "f9c1c496",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Сопрограммы, yield from PEP 0380\n",
    "def grep(pattern):\n",
    "    print(\"start grep\")\n",
    "    while True:\n",
    "        line = yield\n",
    "        if pattern in line:\n",
    "            print(line)\n",
    "    \n",
    "def grep_python_coroutine():\n",
    "    g = grep(\"python\")\n",
    "    yield from g"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "6e7ce4c8",
   "metadata": {},
   "outputs": [],
   "source": [
    "g = grep_python_coroutine() # is g coroutine?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "6c4e848e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<generator object grep_python_coroutine at 0x7f87c4248b48>"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "g"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "9c818942",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "start grep\n"
     ]
    }
   ],
   "source": [
    "g.send(None)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "d5052d2c",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "python wow!\n"
     ]
    }
   ],
   "source": [
    "g.send(\"python wow!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff9ded26",
   "metadata": {},
   "source": [
    "То есть, мы используем инструкцию `yield from` и указываем объект в виде другой корутины. Теперь, если мы попробуем вызвать нашу функцию `grep_python_coroutine`, она будет являться корутиной или генератором, и для того, чтобы продолжить и выполнить код, который находится у нее внутри, необходимо вызвать метод `send`, передать туда значение `none` и далее вызвать метод `send` и передать нужную строчку. Таким образом, в Python стало очень легко и удобно вызывать из одной корутины другую.\n",
    "\n",
    "Для обычных генераторов инструкцию `yield` можно использовать как замену цикла `for`, внутри которого вызывается инструкция `yield`. Рассмотрим пример."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "ffe0106e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# PEP 380, генераторы\n",
    "def chain(x_iterable, y_iterable):\n",
    "    yield from x_iterable\n",
    "    yield from y_iterable"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "47f4f8b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "a = [1, 2, 3]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "00dc65e5",
   "metadata": {},
   "outputs": [],
   "source": [
    "b = (4, 5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "f92a3508",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "2\n",
      "3\n",
      "4\n",
      "5\n"
     ]
    }
   ],
   "source": [
    "for x in chain(a, b):\n",
    "    print(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21998335",
   "metadata": {},
   "source": [
    "Например, у нас есть два объекта, по которым возможно осуществлять итерацию. Это список `a = [1, 2, 3]` и `tuple`, который мы запоминаем в переменную `b = (4, 5)`. Далее, мы в цикле вызываем функцию `chain`, передаем туда эти списки и выводим то, что нам генерирует наша функция `chain`. В функции `chain` мы используем конструкцию `yield from` и передаем туда объект, по которому возможна итерация. По сути, это то же самое, если мы функцию перепишем в таком виде"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7a82b907",
   "metadata": {},
   "outputs": [],
   "source": [
    "def the_same_chain(x_iterable, y_iterable):\n",
    "    for x in x_iterable:\n",
    "        yield x\n",
    "        \n",
    "    for y in y_iterable:\n",
    "        yield y"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f8ff665d",
   "metadata": {},
   "source": [
    "то `yield from` будет работать точно так же, как цикл `for`, внутри которого вызвана инструкция `yield`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c754a0cd",
   "metadata": {},
   "source": [
    "Итак, на этой лекции мы рассмотрели то, как устроены корутины в Python, а также рассмотрели их сходство с генераторами и различия.\n",
    "\n",
    "Еще раз хочу обратить ваше внимание на то, что генераторы производят значение, а корутины потребляют значение. Корутина может замораживать свое состояние, и восстанавливается это состояние, после того как в нее отправили данные при помощи вызова метода `send`. При помощи вызова `close` можно завершать выполнение корутины, передавать исключения в нее. Также в Python 3 можно использовать конструкцию `yield from` для вызова одной корутины в другой. Все эти знания потребуются нам для того, чтобы понять, как устроен фреймворк `asyncio` и в следующих лекциях мы попробуем разобраться с устройством этого фреймворка."
   ]
  }
 ],
 "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.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
