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

Протокол генератора в Python и выражение yield

Содержание:


Понятие генератор.

Тип generator (генератор) похож на тип iterator (итератор), но тип generator не хранит значения последовательности в памяти, а генерирует/вычисляет результат на лету - в ходе каждого вызова, тем самым экономит память и вычислительные ресурсы.

Функция считается генератором, если:

  • Cодержит одно или несколько выражений yield.
  • При вызове возвращает объект типа generator, но не начнет выполнение.
  • Методы __iter__() и __next__() реализуются автоматически.
  • После каждого вызова функция приостанавливается, а управление передается вызывающей стороне.
  • Локальные переменные и их состояния запоминаются между последовательными вызовами.
  • Когда вычисления заканчиваются по какому то условию, автоматически вызывается StopIteration.

Важно! Так как генератор - это "улучшенный" итератор, следовательно на тип generator распространяются такие же ограничения как и тип тип iterator.

Выражение yield.

Выражение yield предоставляют удобный способ реализации протокола итератора, который технически, представляет из себя объект генератора. Это выражение используется в теле функции и приводит к тому, что функция становится генератором. Выражение yield используется примерно как return, отличие в том, что функция возвращает генератор.

Функции-генераторы так же могут включать инструкцию return, которая завершает генерацию значений, возбуждая исключение StopIteration после выполнения обычного выхода из функции. С точки зрения вызывающей программы, метод generator.__next__() генератора возобновляет выполнение функции, пока она не встретит следующую инструкцию yield или пока не возбудит исключение StopIteration.

Принцип работы генератора.

Выполнение начинается при вызове одного из методов генератора. В это время выполнение переходит к первому выражению yield, где он снова приостанавливается, возвращая значение expression_list, объекту, вызвавшему генератор. Под приостановкой, подразумевается сохранение всех локальных состояний, включая текущие привязки локальных переменных, указатель инструкции, внутренний стек вычислений и состояние любой обработки исключений.

Когда выполнение возобновляется вызовом одного из методов генератора, то он может действовать точно так же, как если бы выражение yield было просто другим внешним вызовом. Значение выражения yield после возобновления зависит от метода, который возобновил выполнение. Если используется метод generator.__next__() (обычно через for ... in или функцию next()), то результат отсутствует. В противном случае, если используется метод generator.send(), то результатом будет значение, переданное этому методу.

Выражения yield допускаются в любом месте конструкции try ... expect. Если генератор не возобновляется до его завершения (при достижении нулевого числа ссылок или при сборе мусора), то вызывается метод generator.close() генератора, позволяющий инструкции finally в конструкции try ... expect, выполнить любой завершающий код.

Пример демонстрирующий методы и поведение генератора:

def echo(value=None):
    print("next() вызвали в первый раз - началось выполнение.")
    try:
        while True:
            try:
                value = (yield value)
                # Приостановка. Все локальное состояние 
                # сохраняется до следующего вызова
            except Exception as e:
                value = e
    finally:
        print("Вызван метод close(), не забудьте очистить память.")
        
generator = echo(1)
print(next(generator))
# next() вызвали в первый раз - началось выполнение.
# 1
print(next(generator))
# None
print(generator.send(2))
# 2
print(generator.throw(TypeError, "- Error -"))
# - Error -
generator.close()
# Вызван метод close(), не забудьте очистить память.

Выражение yield from <expression>.

Если в генераторе используется выражение yield from <expression>, то он обрабатывает предоставленное выражение <expression> как другой итератор. Все значения, выданные этим под-итератором, передаются непосредственно вызывающей стороне текущего генератора.

Если выражение <expression> это генератор, то любые значения, передаваемые с помощью метода generator.send() и любые исключения, передаваемые с помощью метода generator.throw(), передаются базовому генератору в соответствующие методы. Если соответствующие методы не определены, то generator.send() вызовет исключение AttributeError или TypeError, в то время как generator.throw() просто немедленно вызовет переданное исключение.

Когда базовый итератор завершен, то атрибут value возвращает исключения StopIteration, которое становится значением выражения yield. Оно может быть установлено либо явно при появлении StopIteration, либо автоматически, когда под-итератор <expression> является генератором и возвращает значение.

Дополнительно смотрите раздел "Выражение-генератора yield from <expression>".

Расширенная реализация/протокол типа generator.

Обратите внимание, что вызов любого из методов генератора, когда генератор уже выполняется, вызывает исключение ValueError.

generator.__next__():

Запускает выполнение функции генератора или возобновляет его при последнем выполненном выражении yield. Когда функция генератора возобновляется с помощью метода __ next__ (), текущее выражение yield всегда возвращает как None. Затем выполнение продолжается до следующего выражения yield, где генератор снова приостанавливается, а значение expression_list возвращается объекту вызвавшему __next__(). Если генератор завершает работу без получения другого значения, возникает исключение StopIteration.

Этот метод обычно вызывается неявно, например, с помощью for ... in цикла или встроенной next() функции.

generator.send(value):

Метод возобновляет выполнение и “отправляет " значение в функцию генератора. Аргумент value становится результатом текущего выражения yield. Метод generator.send() возвращает следующее значение, полученное генератором, или вызывает StopIteration, если генератор завершает работу без получения другого значения. Когда generator.send() вызывается для запуска генератора, он должен быть вызван с аргументом None, поскольку нет выражения yield, которое могло бы получить значение.

Метод generator.send(value) может использоваться, например, чтобы реализовать генератор, который можно будет завершать из вызывающей программы или переустанавливать в нем текущую позицию в последовательности результатов.

generator.throw(value)
generator.throw(type[, value[, traceback]])
:

Изменено в версии 3.12: вторая сигнатура generator.throw(type[, value[, traceback]]) устарела и может быть удалена в будущей версии Python.

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

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

Для обеспечения обратной совместимости поддерживается вторая подпись в соответствии с соглашением, принятым в более старых версиях Python. Аргумент type должен быть классом исключений, а value - экземпляром исключения. Если значение не указано, то вызывается конструктор type для получения экземпляра. Если предоставляется обратная трассировка, то она устанавливается в качестве исключения, в противном случае любой существующий атрибут __traceback__, хранящийся в значении, может быть очищен.

generator.close():

Метод вызывает исключение GeneratorExit в точке, где функция генератора была приостановлена. Если функция генератора затем завершает работу корректно, уже закрыта или вызывает GeneratorExit (не улавливая исключение), close возвращается к вызывающему объекту. Если генератор выдает значение, то возникает ошибка RuntimeError. Если генератор вызывает любое другое исключение, оно передается вызывающему объекту. Метод generator.close() ничего не делает, если генератор уже вышел из-за исключения или нормального выхода..

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