Сообщить об ошибке.

Передача значений в генератор

В более ранних версиях Python генераторы могли выдавать только выходные данные. После того, как код генератора был вызван для создания итератора, не было никакого способа передать какую-либо новую информацию в функцию при возобновлении выполнения. Что бы обойти такое поведение, можно было заставить генератор обратиться к глобальной переменной или передать некоторый изменяемый объект, который затем модифицируют вызов.

С версии Python-2.5 есть простой способ передать значения в генератор. Оператор yield стал выражением, возвращающим значение, которое может быть присвоено переменной или обработано иным образом:

val = (yield i)

Всегда ставьте скобки вокруг выражения yield, когда что-то делаете с возвращаемым значением, как в приведенном выше примере. Скобки не всегда необходимы, но их всегда лучше добавить, а не вспоминать, когда они нужны.

Выражение yield всегда должно заключаться в скобки, за исключением случаев, когда оно встречается в выражении верхнего уровня в правой части присваивания (PEP 342). Это означает, что можно написать val = yield i, но выражение yield i должно использовать скобки, когда есть операция, такая как val = (yield i) + 12.

Значения отправляются в генератор путем вызова его метода .send(value). Этот метод возобновляет код генератора, а выражение yield возвращает указанное значение value. Если вызывается обычный метод __next__(), то yield возвращает None.

Вот простой счетчик, который увеличивается на 1 и позволяет изменить значение внутреннего счетчика.

def counter(maximum):
    i = 0
    while i < maximum:
        val = (yield i)
        # Если указано значение, то изменяем счетчик
        if val is not None:
            i = val
        else:
            i += 1

И вот пример изменения счетчика:

>>> it = counter(10)  
>>> next(it)  
# 0
>>> next(it)  
# 1
>>> it.send(8)  
# 8
>>> next(it)  
# 9
>>> next(it)  
# Traceback (most recent call last):
#   File "test.py", line 15, in <module>
#     it.next()
# StopIteration

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

В генераторах есть два других метода:

  • метод .throw(type, value=None, traceback=None) используется для вызова исключения внутри генератора. Исключение вызывает выражение yield, где выполнение генератора приостановлено.
  • метод .close() вызывает исключение GeneratorExit внутри генератора, чтобы завершить итерацию. При получении этого исключения код генератора должен либо вызвать GeneratorExit, либо StopIteration. Перехват исключения и выполнение чего-либо еще недопустимо и вызовет исключение RuntimeError. Метод .close() также будет вызываться сборщиком мусора в Python.

Если необходимо запустить код очистки при возникновении исключения GeneratorExit, то это можно сделать использовав try: ... finally: suite вместо перехвата исключения GeneratorExit.

Совокупный эффект этих изменений заключается в том, чтобы превратить генераторы из односторонних производителей информации в производителей и потребителей.

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