В более ранних версиях 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
).