Что такое генераторы: ГЕНЕРАТОР | это… Что такое ГЕНЕРАТОР?

что это такое и зачем они нужны / Skillbox Media

#статьи

  • 11

Генераторы используют, чтобы оперативная память не давилась большими объёмами информации. В Python это фишки, экономящие память.

Vkontakte Twitter Telegram Скопировать ссылку

 vlada_maestro / shutterstock

Марина Демидова

Программист, консультант, специалист по документированию. Легко и доступно рассказывает о сложных вещах в программировании и дизайне.

Допустим, у вас есть файл, который весит десяток гигабайт. Из него нужно выбрать и обработать строки, подходящие под какое-то условие, а то и сравнить со строками другого большого файла.

Другой пример: нужно проанализировать практически бесконечный поток данных. Это могут быть, например, показания счётчиков, биржевые котировки, сетевой трафик.

А может, нужно создать поток данных самостоятельно: рассчитать комбинаторную структуру для определения вероятности какого-то события, математическую последовательность или последовательность случайных чисел.

Что делать? Хранить такие объёмы данных в компьютере нереально: они не поместятся в оперативную память — а некоторые и на жёсткий диск. Выход один — обрабатывать информацию небольшими порциями, чтобы не вызывать переполнения памяти. В Python на этот случай есть специальный инструмент — генераторы.

  • Генератор — это объект, который сразу при создании не вычисляет значения всех своих элементов.
  • Он хранит в памяти только последний вычисленный элемент, правило перехода к следующему и условие, при котором выполнение прерывается.
  • Вычисление следующего значения происходит лишь при выполнении метода next(). Предыдущее значение при этом теряется.

Этим генераторы отличаются от списков — те хранят в памяти все свои элементы, и удалить их можно только программно. Вычисления с помощью генераторов называются ленивыми, они экономят память.

Рассмотрим пример: создадим объект-генератор gen с помощью так называемого генераторного выражения. Он будет считать квадраты чисел от 1 до 4 — такую последовательность создаёт функция range(1,5).

>>> a = (i**2 for i in range(1,5))
>>> a
<generator object <genexpr> at 0x0000023A7524D6D0>
>>> next(a)
1
>>> next(a)
4
>>> next(a)
9
>>> next(a)
16
>>> next(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Когда мы выведем на консоль переменную gen, то увидим лишь сообщение, что это объект-генератор.

При четырёх вызовах метода next(a) будут по одному рассчитываться и выводиться на консоль значения генератора: 1, 4, 9, 16. Причём в памяти будет сохраняться только последнее значение, а предыдущие сотрутся.

Когда мы попытаемся вызвать next(gen) в пятый раз, генератор сотрёт из памяти последний элемент (число 16) и выдаст исключение StopIteration.

>>> next(gen)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Всё! Генератор больше не работает. Сколько бы мы ни вызывали next(gen), ничего считаться не будет. Чтобы запустить генератор ещё раз, придётся создавать его заново.

Нет, значения можно вычислять в цикле for. В этом случае метод next() вызывается неявно. Например:

>>> a = (i**2 for i in range(1,5))
>>> for i in a:
. ..     print(i)
1
4
9
16

Когда весь цикл пройден, произойдёт исключение StopIteration. Хотя на консоль сообщение об этом не выводится, но генератор помнит о нём и больше работать не будет. То есть цикл for можно запускать только один раз, во второй раз не получится. Нельзя об этом забывать.

Для этого сначала рассмотрим упрощённый способ создания генератора — с помощью генераторного выражения.

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

(выражение for j in итерируемый объект if условие)

Где for, in, if — ключевые слова, j — переменная.

Пример генераторного выражения мы рассмотрели выше. Теперь посмотрим, как можно применить его для обработки большого файла.

Перед нами задача: на сервере есть огромный журнал событий log.txt, в котором хранятся сведения о работе какой-то системы за год. Из него нужно выбрать и обработать для статистики данные об ошибках — строки, содержащие слово error.

Такие строки можно выбрать и сохранить в памяти с помощью списка:

with open(path + "\log.txt", "r") as log_file:
     err_list = [st for st in log_file if "error" in st]

Здесь path — путь к файлу log. В результате сформируется список вида:

[строка1, строка2, строка3, ….]

В списке e_l содержатся все строки со словом error, они записаны в память компьютера. Теперь их можно обработать в цикле. Недостаток метода в том, что, если таких строк будет слишком много, они переполнят память и вызовут ошибку MemoryError.

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

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

with open("path\log.txt", "r") as log_file:
     err_gen = (st for st in log_file if "error" in st)
     for item in err_gen:
         <обработка строки item>  
  • Генераторное выражение возвращает объект-генератор err_gen.
  • Генератор начинает в цикле выбирать из файла по одной строке со словом error и передавать их на обработку.
  • Обработанная строка стирается из памяти, а следующая записывается и обрабатывается. И так до конца цикла.

Этот метод не вызывает переполнения, так как в каждый момент времени в памяти находится только одна строка. При этом нужный для работы объём памяти не зависит от размера файла и количества строк, удовлетворяющих условию.

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

Генераторные выражения — это упрощённый вариант функций-генераторов, также создающих генераторы.

Функция-генератор отличается от обычной функции тем, что вместо команды return в ней используется yield. И если return завершает работу функции, то инструкция yield лишь приостанавливает её, при этом она возвращает какое-то значение.

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

Чтобы было понятнее, рассмотрим небольшой пример:

>>> def f_gen(m):
...     s = 1
...     for n in range(1,m):
...         yield n**2 + s
...         s += 1
...
>>> a = f_gen(5)
>>> a
<generator object f_gen at 0x0000023EE468D6D0>
>>> for i in a:
...     print(i)
...
2
6
12
20
>>>

Здесь функция f_gen(5) при вызове создаёт генератор a. Мы видим это, когда выводим a на консоль.

Посчитаем значения генератора в цикле for.

  • При первой итерации выполняется код функции до yield: переменная s =1, n = 1, yield возвращает 2.
  • При второй итерации выполняется оператор после yield, далее к началу цикла и опять до 
    yield
    : s = 2, n = 2, yield возвращает 6.
  • Соответственно, при третьей и четвёртой итерации генерируются значения 12 и 20, после чего выполнение генератора прекращается.

Как видим, значения переменных n и s между вызовами сохраняются.

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

Рассмотрим, как можно с помощью генератора создать математическую последовательность, например, программу, генерирующую простые числа (напоминаем, это числа, не имеющие делителей, кроме 1).

Наша программа будет последовательно анализировать целые числа больше 1. Для каждого числа n программа ищет делители в диапазоне от 2 до √n. Если делители есть, программа переходит к следующему числу. Если их нет, значит, n — число простое, и программа выводит его на печать.

>>> import math
>>> def prime_num():
...	 nm = 2
... 	while True:
...     	sq = math.ceil(nm**1/2)
...     	for i in range(2, sq+1):
...         	if (nm % i) == 0:
...            	break
...     	else:
...         	yield nm
...     	nm += 1
...
>>> for num in prime_num():
... 	print(num)
...
2
3
5
7
11
13
17
19
23
29
31

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

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

Когда-то был один next (), но в Python 2.5 появилось ещё три метода:

  • . close () — останавливает выполнение генератора;
  • .throw () — генератор бросает исключение;
  • .send () — интересный метод, позволяет отправлять значения генератору.

Рассмотрим пару небольших примеров.

Сначала на .close () и .throw ():

>>> def f_gen():
...     n = 1
...     while True:
...         yield n**2
...         n += 1
...
>>> generator1 = f_gen()
>>> generator2 = f_gen()
>>>
>>> for i in generator1:
...     print(i)
...     if i > 10:
...         generator1.close()
...
1
4
9
16
>>> for i in generator2:
...      print(i)
...      if i > 20:
...           generator2.throw(Exception("Плохо!"))
...
1
4
9
16
25
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 4, in f_gen
Exception: Плохо!

Программа создаёт два генератора, возвращающих бесконечную последовательность квадратов чисел. Их выполнение прекращается с помощью методов .close() и .throw().

Пример использования .send()

>>> def generator(x):
...     while True:
...         x = yield x + 1
...
>>> g = generator(5)
>>> g.send(None)
6
>>> g.send(10)
11
>>> g.send(15)
16
>>> g.send(4)
5

Здесь мы не получаем значения генератора, а отправляем их на обработку с помощью метода .send().

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

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

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

Читайте также:

Vkontakte Twitter Telegram Скопировать ссылку

Учись бесплатно:
вебинары по&nbspпрограммированию, маркетингу и&nbspдизайну.

Участвовать

«Яндекс» выпустил расширенную версию корпоративного браузера 03 мар 2023

Вышла криптоальтернатива Twitter под названием Bluesky 01 мар 2023

Meta* представила свою нейросеть для исследователей в области ИИ 28 фев 2023

Понравилась статья?

Да

Генераторы в Python. Оператор yield. Урок 11 курса «Объектно-ориентированное программирование на Python»

Генераторы можно считать подвидом итераторов, а способ их создания – инструментом для создания несложных итераторов.

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

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

У генераторов методы __next__() и __iter__() создаются средствами самого языка, то есть автоматически. Программисту их определять не надо, что упрощает создание пользовательских типов итераторов.

>>> def starmaker(n):
...     while n > 0:
...             yield '*'
...             n -= 1
... 
>>> type(starmaker)
>class 'function'>
>>> s = starmaker(3)
>>> type(s)
>class 'generator'>
>>> next(s)
'*'
>>> next(s)
'*'
>>> next(s)
'*'
>>> next(s)
Traceback (most recent call last):
  File ">stdin>", line 1, in >module>
StopIteration

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

Если мы сделаем нечто подобное

>>> def g():
...     yield 1
... 

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

>>> a = g()
>>> next(a)
1
>>> next(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Обратите внимание, что функция starmaker() делает то же самое, что класс, описанный в прошлом уроке:

>>> class A:
...     def __init__(self, qty):
...             self.qty = qty
...     def __iter__(self):
...             return self
...     def __next__(self):
...             if self.qty > 0:
...                     self.qty -= 1
...                     return '+'
...             else:
...                     raise StopIteration
... 
>>> a = A(3)
>>> for i in a:
...     print(i)
... 
+
+
+

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

Генераторные выражения

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

Синтаксис генераторных выражений подобен генераторам списков, рассматриваемых в курсе «Python. Введение в программирование». Однако, в отличие от списков, в случае генераторов используются круглые скобки.

Напомним, как выглядят генераторы списков и то, что возвращают они списковый тип данных:

>>> a = [i+1 for i in range(10)]
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> type(a)
<class 'list'>
>>> import random
>>> b = [random.randint(0,9) for i in range(5)]
>>> b
[2, 5, 5, 2, 9]
>>> c = [i for i in b if i % 2 == 0]
>>> c
[2, 2]

Результат выражения, стоящего до for, добавляется на каждой итерации цикла в итоговый список. Выполнение выражения генератора списка сразу заполняет список.

В случае генераторных выражений создается объект-генератор, у которого будет вычисляться очередной элемент только при каждом вызове next():

>>> a = (i+1 for i in range(10))
>>> a
<generator object <genexpr> at 0x7fa586339f10>
>>> type(a)
<class 'generator'>
>>> next(a)
1
>>> next(a)
2

Пример со звездочкой с помощью генераторного выражения будет выглядеть так:

>>> d = ('*' for i in range(5))
>>> for i in d:
...     print(i)
... 
*
*
*
*
*

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

Практическая работа

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

Курс с примерами решений практических работ:
pdf-версия, android-приложение


генераторов в Python — Geeksforgeeks

Связанные статьи

    Напишите статью

  • Написать опыт обследования
  • ВВЕДЕНИЕ

    Вход

    Операторы

    Типы данных

    . Обработка исключений

    Обработка файлов

    Python Regex

    Коллекции Python

    Python Advance

    Python NumPy

    Python Pandas

    Python Django

    Python JSON

    Python CSV

    Python MySQL

    Python MongoDB

    Python OpenCV

    Python Selenium

    Python Tkinter

    Python Kivy

    Data Visualization

    Python Examples and Quiz

Улучшить статью

Сохранить статью

  • Уровень сложности: Easy
  • Последнее обновление: 19 Сен, 2022

  • Читать
  • Обсудить(20+)
  • Улучшить статью

    Сохранить статью

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

    Функция-генератора: Функция-генератор определяется как обычная функция, но всякий раз, когда ей нужно сгенерировать значение, она делает это с ключевым словом yield, а не с возвратом. Если тело определения содержит yield, функция автоматически становится функцией-генератором.

    Python3

    def simpleGeneratorFun():

         yield 1            

         yield 2            

         yield 3            

     

    для значение в simpleGeneratorFun():

         печать (значение)

    Выход

     1
    2
    3
     

    Объект-генератор : Функции-генераторы возвращают объект-генератор. Объекты-генераторы используются либо путем вызова метода next для объекта-генератора, либо с использованием объекта-генератора в цикле for in (как показано в приведенной выше программе).

    Python3

     

    def simpleGeneratorFun():

         yield 1

         yield 2

         yield 3

       

    x = simpleGeneratorFun()

    печать ( след. (x))

    печать ( след. 1 2 3

    Таким образом, функция-генератор возвращает объект-генератор, который является итерируемым, т. е. может использоваться как Iterators. В качестве другого примера ниже приведен генератор чисел Фибоначчи.

    Python3

    def fib(limit):

          

     

    95

         a, b = 0 , 1

     

        

         while a < limit:

             yield a

             a, b = b, a + b

     

    x = fib( 5 )

     

    print ( next (x))

    print ( next (x))

    print ( next (x))

    print ( next (x))

    print ( next (x))

     

    print ( "\nUsing for in loop" )

    for i in fib( 5 ):

         print (i )

    Выход

     0
    1
    1
    2
    3
    Использование for в цикле
    0
    1
    1
    2
    3
     

    Приложения:  

    Предположим, мы создаем поток чисел Фибоначчи, применение генераторного подхода делает его тривиальным; нам просто нужно вызвать next(x), чтобы получить следующее число Фибоначчи, не беспокоясь о том, где или когда заканчивается поток чисел. Более практичным типом потоковой обработки является обработка больших файлов данных, таких как файлы журналов. Генераторы обеспечивают компактный метод такой обработки данных, поскольку в один заданный момент времени обрабатываются только части файла. Мы также можем использовать Iterators для этих целей, но Generator обеспечивает быстрый способ (нам не нужно писать здесь методы __next__ и __iter__).

    Эта статья предоставлена ​​ Shwetanshu Rohatgi . Пожалуйста, пишите комментарии, если вы обнаружите что-то неправильное или если вы хотите поделиться дополнительной информацией по теме, обсуждаемой выше.


    Статьи по теме

    Что нового

    Мы используем файлы cookie, чтобы обеспечить вам максимальное удобство просмотра нашего веб-сайта. Используя наш сайт, вы подтверждаете, что вы прочитали и поняли наши Политика в отношении файлов cookie и Политика конфиденциальности

    Генераторы Python (с примерами)

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

    В Python генератор — это функция, возвращающая итератор, который при повторении создает последовательность значений.

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


    Создать генератор Python

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

     определение имя_генератора (аргумент):
        # заявления
        yield something 

    Здесь ключевое слово yield используется для получения значения из генератора.

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


    Пример: Python Generator

    Вот пример функции-генератора, которая создает последовательность чисел,

     def my_generator(n):
        # инициализировать счетчик
        значение = 0
        # цикл до тех пор, пока counter не станет меньше n
        в то время как значение 

    Выход

      0
    1
    2  

    В приведенном выше примере функция генератора my_generator() принимает целое число n в качестве аргумента и создает последовательность чисел от 0 до n-1 .

    Ключевое слово yield используется для создания значения из генератора и приостановки выполнения функции генератора до тех пор, пока не будет запрошено следующее значение.

    Цикл for перебирает объект-генератор, созданный my_generator() , а оператор печати печатает каждое значение, созданное генератором.

    Мы также можем создать объект генератора из функции генератора, вызвав функцию, как и любую другую функцию, например,

     генератор = мой_диапазон (3)
    print(следующий(генератор)) # 0
    печать (следующий (генератор)) # 1
    print(next(generator)) # 2 

    Выражение генератора Python

    В Python выражение генератора — это краткий способ создания объекта генератора.

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

    Синтаксис выражения генератора

    Выражение генератора имеет следующий синтаксис:

     (выражение для элемента в итерации) 

    Здесь выражение — это значение, которое будет возвращено для каждого элемента в итерации .

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


    Пример 2. Выражение генератора Python

     # создать объект генератора
    Squares_generator = (i * i для i в диапазоне (5))
    # перебрать генератор и вывести значения
    для i в генераторе квадратов:
        print(i) 

    Вывод

      0
    1
    4
    9
    16  

    Здесь мы создали объект-генератор, который будет производить квадраты чисел 9от 0084 0 до 4 при повторении.

    Затем, чтобы перебрать генератор и получить значения, мы использовали цикл for .


    Использование генераторов Python

    Есть несколько причин, по которым генераторы являются мощной реализацией.

    1. Простота реализации

    Генераторы могут быть реализованы ясным и лаконичным способом по сравнению с аналогами класса итераторов. Ниже приведен пример реализации последовательности степени 9.0084 2 с использованием класса итератора.

     класс PowTwo:
        def __init__(сам, макс=0):
            самоп = 0
            селф.макс = макс
        защита __iter__(я):
            вернуть себя
        деф __следующий__(сам):
            если self.n > self.max:
                поднять StopIteration
            результат = 2 ** self.n
            самоп += 1
            return result 

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

     по умолчанию PowTwoGen (макс. = 0):
        п = 0
        в то время как n < макс:
            выход 2 ** п
            п += 1 

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

    2. Эффективное использование памяти

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

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

    3. Представляет бесконечный поток

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

    Следующая функция генератора может генерировать все четные числа (по крайней мере, теоретически).

     по умолчанию all_even():
        п = 0
        пока верно:
            выход n
            n += 2 

    4. Конвейерные генераторы

    Можно использовать несколько генераторов для конвейерной последовательности операций. Лучше всего это проиллюстрировать на примере.

    Предположим, у нас есть генератор, который производит числа в ряду Фибоначчи. И у нас есть еще один генератор для возведения чисел в квадрат.

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

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *