Инструкция 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)
Можете угадать вывод?