DevGang
Авторизоваться

Функции генератора в Python

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

По определению, генераторы — это функции, содержащие один или несколько yield операторов. Этот yield оператор аналогичен оператору return за исключением того, что при его обнаружении он не приводит к немедленному завершению функции. Вместо этого он заставляет функцию приостановить свое выполнение и возвращает заданное значение (если оно есть) вызывающей стороне. Затем выполнение можно возобновить, вызвав функцию next() генератора.

Рассмотрим следующий пример:

def func():
    print('Hello, World!')
    yield 1
    print('You are at Pynerds')
    yield 2
    print('Enjoy Your stay')
    yield 3

gen = func()
print(next(gen))
print('....................')
print(next(gen))
print('....................')
print(next(gen))

Вывод:

Hello, World!
1
....................
You are at Pynerds
2
....................
Enjoy Your stay
3

Как показано выше, когда вызывается функция-генератор, она возвращает объект-генератор, который является типичным итератором. Будучи итератором, объект поддерживает протокол итератора, а это означает, что его можно использовать с функцией next() и другими конструкциями итерации, такими как циклы for.

Функция next() заставляет генератор запустить или возобновить выполнение до тех пор, пока не встретится другой оператор yield или пока выполнение не завершится. Мы также можем использовать генератор с циклом for, который автоматически вызывает функцию генератораnext(), пока она не достигнет конца выполнения. 

def func():
   L = [10, 20, 30, 40, 50, 60]
   for i in L:
       yield i

for i in func():
    print(i)

Как показано в приведенном выше примере, оператор yield можно использовать в любом месте тела функции, например, в циклах, блоках условий и т. д.

Как работают генераторы

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

С другой стороны, всякий раз, когда объект-генератор встречает оператор yield, он временно приостанавливает выполнение функции и сохраняет свое состояние. Это позволяет генератору «запомнить», где он остановился, когда его вызывают снова, и он может возобновить выполнение с этого момента, а не начинать все сначала. Это делает генераторы очень эффективными при работе с большими наборами данных или длительными вычислениями, поскольку позволяет получать результаты порциями, а не загружать весь набор данных в память или ждать завершения длительного вычисления перед возвратом результатов.

def my_gen():
   data = range(10)
   print("started")
   for i in data:
       if i == 5:
           print('We are halfway.')
       yield i
   print('Done')

gen = my_gen()
for i in gen:
    print(i)

Генератор с оператором возврата

Когда оператор return встречается в объекте-генераторе, генератор закрывается, и любые последующие вызовы приведут к исключению StopIteration.

def func():
   data = range(10)
   for i in data:
       yield i
       if i == 5:
           return

for i in func():
    print(i)

Функция next()

Встроенная функция next() — полезная функция при работе с итераторами. Он используется для получения следующего элемента из объекта-итератора. Функция вызывает исключение StopIteration, если итератор пуст.

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

def func():
   L = range(10)
   for i in L:
       yield i

gen = func()
L = list(gen)

print(*L)

#The generator object is now empty
next(gen)

Как показано выше, попытка доступа к следующему значению в пустом объекте-генераторе вызовет исключение StopIteration. Мы можем использовать блоки try для перехвата исключения StopIteration.

def func():
   L = [1, 4, 8, 9]
   for i in L: 
      yield i

gen = func()
try:
    while True:
        value = next(gen)
        print(value)
except StopIteration:
    print("Nothing else")

Закрытие генератора

Объекты генератора содержат метод close(), который можно использовать для преждевременного закрытия генератора и освобождения любых используемых ресурсов.

def func():
    data = ['Python', 'Java', 'C++', 'Ruby', 'Swift']
    for i in data:
        yield i

gen = func()
print(next(gen))
print(next(gen))
print(next(gen))

#close the generator
gen.close()

#subsuent calls will raise a StopIteration exception
next(gen)

Управление генератором снаружи

Когда мы выполняем последующие вызовы генератора, мы можем использовать этот send()метод для отправки значения генератору. Этот метод можно рассматривать частично как противоположность утверждению yield . Он возобновляет выполнение функции и получает полученное значение так же, как и функция next(), но также отправляет внешнее значение обратно в yield оператор.

Синтаксис:

send(obj)

Отправленный объект получен оператором yield. Мы можем отправить только один объект.

def square():
    recieved = None
    while True:
        if recieved is None: 
            recieved = yield "No value given."
        else:
            recieved = yield f"{recieved} ^ 2 = {recieved ** 2}"

gen = square()
print(gen.send(None))
print(gen.send(5))
print(gen.send(10))
print(gen.send(21))
print(gen.send(25))
print(gen.send(30))
print(gen.send(50))

gen.close()

Обратите внимание, что мы не можем отправить значение (кроме None) в первом вызове yield.

Получить внутреннее состояние объекта-генератора

Объекты-генераторы содержат некоторые атрибуты, которые мы можем использовать для получения его внутреннего состояния. Например, атрибуты gi_suspended и gi_running возвращают логическое значение, указывающее, находится ли объект-генератор в настоящий момент на паузе или работает соответственно.

def func():
    data = [1, 2, 3, 4, 5]
    for i in data:
        yield i

gen = func()

print(next(gen))

print('running: ', gen.gi_running)
print('suspended: ', gen.gi_suspended)

Вывод:

1
running:  False
suspended:  True 

Ленивое вычисление в функциях-генераторах

Ленивое вычисление — это метод оптимизации, при котором вычисление выражения откладывается до тех пор, пока не потребуется значение. Функции-генераторы используют этот метод таким образом, что возвращаемые значения не загружаются в память все сразу, вместо этого элементы эффективно обрабатываются по одному по мере необходимости. Это делает их способными эффективно обрабатывать большие наборы данных, которые в противном случае заняли бы много пространства памяти.

Источник:

#Python
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу