Module (5%)
Section (31%)

Инструкция yield

Протокол итератора не является особенно трудным для понимания и использования, но также несомненно, что протокол достаточно неудобен.

Основной дискомфорт, который он приносит - это необходимость сохранения состояния итерации между последующими __iter__ вызовами.

Например, итератор Fib вынужден хранить точное место, в котором был остановлен последний вызов (т.е. оцененное число и значения двух предыдущих элементов). Это делает код более понятным.

 

Вот почему Python предлагает гораздо более эффективный, удобный и элегантный способ написания итераторов.

Концепция основана на очень специфичном и мощном механизме, предоставляемом ключевым словом yield.

Вы можете думать о ключевом слове yield как о более умном родственнике оператора return, с одним существенным отличием.

Посмотрите на эту функцию:

def fun(n): for i in range(n): return i

Это выглядит странно, не так ли? Понятно, что цикл for не имеет шансов завершить свое первое выполнение, так как return безвозвратно прервет его.

Более того, вызов функции ничего не изменит - цикл for начнется с нуля и будет немедленно прерван.

Можно сказать, что такая функция не может сохранять и восстанавливать свое состояние между последующими вызовами.

Это также означает, что подобную функцию нельзя использовать в качестве генератора.




Мы заменили ровно одно слово в коде - видите?

def fun(n): for i in range(n): yield i

Мы добавили yield вместо return. Эта небольшая поправка превращает функцию в генератор, а выполнение оператора yield имеет несколько очень интересных эффектов.

Прежде всего, он возвращает значение выражения, указанного после ключевого слова yield, точно так же как return, но не теряет состояние функции.

Все значения переменных заморожены и ждут следующего вызова, когда выполнение будет возобновлено (не с нуля, как после return).

Есть одно важное ограничение: такую функцию не следует вызывать явно, так как на самом деле она больше не является функцией - это объект генератора.

Вызов вернет идентификатор объекта, а не серию, которую мы ожидаем от генератора.

 

По тем же причинам предыдущая функция (та, что с оператором return) может вызываться только явно и не должна использоваться в качестве генератора.

Как построить генератор

Давайте покажем Вам новый генератор в действии.

Вот как мы можем это использовать:

def fun(n): for i in range(n): yield i for v in fun(5): print(v)

Можете угадать вывод?