0:04
В предыдущем видео мы рассмотрели то, как работают генераторы в Python.
В этом видео мы познакомимся с сопрограммами и выясним, в чем
отличие и сходство между генераторами и сопрограммами в Python.
Также мы обсудим, как работает конструкция yield from
и рассмотрим примеры работы различных сопрограмм.
Итак, давайте перейдем к примеру и рассмотрим, как выглядит сопрограмма.
Предположим, наша цель — фильтровать входной поток
при помощи функции grep.
В функцию grep мы передаем некий паттерн, например, это пусть будет
строчка python.
Далее мы должны в эту функцию передать
некие строчки и вывести на экран только те, в которых присутствует
строка python.
При помощи сопрограмм наша функция grep
выглядит следующим образом.
Мы в бесконечном цикле вызываем
такую конструкцию - line присвоить yield.
В этом месте очень важно уделить внимание отличию от
того, как yield используется в генераторах.
В генераторах мы выражение присваивали справа
от конструкции yield, а здесь мы, наоборот, переменной присваиваем
результат работы конструкции yield.
Что произойдет в данном случае?
В данном случае функция как бы заморозит свое значение
и будет ожидать ввода данных
при помощи метода send.
Итак, если мы вызовем функцию grep, будет создана
корутина.
Для того чтобы запустить нашу корутину, необходимо вызвать метод
next.
После того, как метод next будет вызван, запустится код нашей функции,
выведется строчка start grep, запустится бесконечный цикл,
код дойдет до инструкции yield,
и здесь вернется управление в основной поток.
После этого в основном потоке мы отправляем данные нашей корутине,
и код возобновляет свою работу дальше.
Давайте посмотрим, как код будет выполняться в отладчике в консоли.
Нам потребуется код нашего примера
этой утилиты — это наша корутина grep.
Запускаем отладчик при помощи команды python3,
указываем флаг −m pdb
и запускаем наш отладчик.
Давайте разбираться с тем, как будет выполняться наша корутина.
Итак, мы объявили нашу функцию grep.
Далее,
мы создали объект типа генератор.
Давайте еще раз убедимся,
что это действительно генератор.
Наш объект g,
после того как ему присвоили выражение grep("python"),
создался, а вызова пока еще не произошло никакого для этой функции.
Далее мы вызываем метод next (g), и это будет равносильно тому,
что мы бы вызвали метод g.send и туда передали значение None
и эта конструкция запустит нашу корутину.
Итак, мы провалились в нашу функцию,
вывели в консоль
строчку "start grep",
зашли в наш цикл, дошли до инструкции yield, и здесь произошел возврат.
Хочу обратить ваше внимание, как произошел этот возврат.
Он не равносилен инструкции return для функции.
Дело в том, что в объекте g, который является корутиной,
запомнился указатель на фрейм стека, запомнились все локальные переменные.
Функция помнит свое состояние. Она как бы заморозила свое выполнение
в том месте, где была указана инструкция yield.
Теперь наша корутина запущена, она помнит свое состояние,
и она ожидает, пока кто-нибудь вызовет метод send и отправит в нее данные.
Давайте продолжим выполнение.
Итак, мы отправили данные при помощи вызова send.
Мы опять вернулись в нашу функцию, но вернулись именно в то место,
где завершили или заморозили свою работу.
Это инструкция yield.
Инструкций yield может быть несколько.
Давайте посмотрим, чему равна переменная line.
В данном случае наше условие для фильтрации не будет выполнено,
и на экран ничего не напечатается. Давайте продолжим выполнение.
Мы снова в цикле
6:44
Итак, давайте еще раз остановимся на ключевых моментах и отличии
того, как работает корутина по сравнению с генератором.
Здесь мы видим
немного другое использование инструкции yield.
Мы в переменную присваиваем ее значение. Внутри корутины
она не производит значение, она потребляет значение,
и точно так же приостанавливает или замораживает свое состояние,
ждет, пока это значение в нее поступит.
После поступления значения она возобновляет свою работу.
Давайте продолжим изучать особенности корутин
и посмотрим следующий пример.
Иногда необходимо
остановить запущенную корутину.
Делается это при помощи вызова метода close для объекта корутины.
Метод close будет вызван автоматически сборщиком мусора,
но если нам нужно самим остановить корутину, то можно
руками вызвать метод close.
Метод close сгенерирует исключение генератора exit
в том месте, где функция заморозила свое значение, свою работу.
Это исключение нельзя игнорировать, его нужно обрабатывать
и завершать свою работу. Обрабатывать его можно при помощи
стандартной работы с исключениями, при помощи блока try except.
Иногда необходимо передать исключения в саму корутину.
Это делается при помощи вызова метода throw.
Пример точно такой же.
Наша корутина приостановила свою работу на вот этой инструкции,
и если мы отправим
в нее исключение, то оно будет сгенерировано именно в этом месте.
Сгенерированное исключение можно обработать
при помощи блока try except,
в общем, стандартным способом для языка Python.
8:58
Давайте посмотрим еще более сложный пример.
У нас есть функция grep,
У нас есть корутина grep,
у нее внутри есть инструкция yield, и мы хотим вызвать нашу корутину grep
в другой корутине. Как это сделать?
Если мы напишем код, который приведен на слайде, и потом сделаем
данный вызов, то будет ли переменная g являться корутиной?
На самом деле, не будет.
Так как она не содержит инструкции yield, она выполнится сразу,
она будет являться обычной функцией.
Для того чтобы было удобно
вызывать из одних корутин другие,
в Python разработали стандарт
PEP 0380 и в Python 3 его реализовали.
И теперь в Python 3 появилась инструкция yield from.
При помощи нее можно выполнить делегирование
вызова одной корутины в другой.
Наш пример можно переписать следующим образом.
То есть, мы используем инструкцию yield from и указываем
объект в виде другой корутины.
Теперь, если мы попробуем вызвать
нашу функцию grep_python_coroutine, она будет являться
корутиной или генератором,
и для того, чтобы продолжить
и выполнить код, который находится у нее внутри, необходимо
вызвать метод send, передать туда значение none и далее вызвать метод
send и передать нужную строчку.
Таким образом,
в Python стало очень легко и удобно вызывать из одной корутины другую.
Для обычных генераторов
инструкцию yield можно использовать
как замену цикла for, внутри которого вызывается инструкция yield.
Рассмотрим пример. Например, у нас есть
два объекта, по которым возможно осуществлять итерацию.
Это список a = [1, 2, 3] и tuple, который мы запоминаем в переменную b = (4, 5).
Далее, мы в цикле вызываем функцию chain, передаем туда эти списки
и выводим то, что нам генерирует наша функция chain.
В функции chain мы используем конструкцию yield from
и передаем туда объект, по которому возможна итерация.
По сути, это то же самое, если мы функцию перепишем в таком виде,
то yield from будет работать точно так же, как цикл for, внутри
которого вызвана инструкция yield.
11:58
Итак, в этом видео мы рассмотрели
то, как устроены корутины в Python,
а также рассмотрели
их сходство с генераторами и различия.
Еще раз
хочу обратить ваше внимание на то, что генераторы
производят значение, а корутины потребляют значение.
Корутина может замораживать свое состояние,
и восстанавливается это состояние, после того как в нее
отправили данные при помощи вызова метода send.
При помощи вызова close можно завершать выполнение корутины,
передавать исключения в нее.
Также в Python 3 можно
использовать конструкцию yield from для вызова одной корутины в другой.
Все эти знания потребуются нам для того, чтобы понять,
как устроен фреймворк asyncio и в следующих видео мы попробуем
разобраться с устройством этого фреймворка.