You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

609 lines
26 KiB
Plaintext

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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