Вопрос-Ответ

Understanding generators in Python

Понимание генераторов в Python

В данный момент я читаю поваренную книгу по Python и в настоящее время изучаю генераторы. Мне трудно прийти в себя.

Поскольку я знаком с Java, существует ли эквивалент Java? В книге говорилось о "производителе / потребителе", однако, когда я слышу это, я думаю о потоковой обработке.

Что такое генератор и зачем вы его используете? Очевидно, без цитирования каких-либо книг (если только вы не можете найти достойный, упрощенный ответ прямо из книги). Возможно, с примерами, если вы чувствуете щедрость!

Переведено автоматически
Ответ 1

Примечание: в этом посте предполагается синтаксис Python 3.x.

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

Обычные функции возвращают единственное значение с помощью return, как и в Java. Однако в Python есть альтернатива, называемая yield . Использование yield в любом месте функции превращает ее в генератор. Обратите внимание на этот код:

>>> def myGen(n):
... yield n
... yield n + 1
...
>>> g = myGen(6)
>>> next(g)
6
>>> next(g)
7
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

Как вы можете видеть, myGen(n) это функция, которая выдает n и n + 1. Каждый вызов next выдает одно значение, пока не будут получены все значения. for циклы вызывают next в фоновом режиме, таким образом:

>>> for n in myGen(6):
... print(n)
...
6
7

Точно так же существуют выражения генератора, которые предоставляют средства для краткого описания определенных распространенных типов генераторов:

>>> g = (n for n in range(3, 5))
>>> next(g)
3
>>> next(g)
4
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

Обратите внимание, что выражения генератора во многом похожи на понимание списков:

>>> lc = [n for n in range(3, 5)]
>>> lc
[3, 4]

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

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

Теперь вы можете спросить: зачем использовать генераторы? Есть пара веских причин:


  • Некоторые концепции можно описать гораздо более кратко, используя генераторы.

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

  • Генераторы позволяют естественным образом описывать бесконечные потоки. Рассмотрим, например, числа Фибоначчи:


    >>> def fib():
    ... a, b = 0, 1
    ... while True:
    ... yield a
    ... a, b = b, a + b
    ...
    >>> import itertools
    >>> list(itertools.islice(fib(), 10))
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

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



  О Python <=2.6: в приведенных выше примерах next есть функция, которая вызывает метод __next__ для данного объекта. В Python <=2.6 используется немного другая техника, а именно o.next() вместо next(o). В Python 2.7 есть next() call .next, поэтому вам не нужно использовать следующее в 2.7:

>>> g = (n for n in range(3, 5))
>>> g.next()
3
Ответ 2

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

>>> def myGenerator():
... yield 'These'
... yield 'words'
... yield 'come'
... yield 'one'
... yield 'at'
... yield 'a'
... yield 'time'

>>> myGeneratorInstance = myGenerator()
>>> next(myGeneratorInstance)
These
>>> next(myGeneratorInstance)
words

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

>>> for word in myGeneratorInstance:
... print word
These
words
come
one
at
a
time

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

>>> from time import gmtime, strftime
>>> def myGen():
... while True:
... yield strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
>>> myGeneratorInstance = myGen()
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:17:15 +0000
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:18:02 +0000

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

Ответ 3

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

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

Все же было бы неплохо быть точным и избегать термина "генератор" без дальнейших уточнений.

Ответ 4

Генераторы можно рассматривать как сокращение для создания итератора. Они ведут себя как итератор Java. Пример:

>>> g = (x for x in range(10))
>>> g
<generator object <genexpr> at 0x7fac1c1e6aa0>
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> list(g) # force iterating the rest
[3, 4, 5, 6, 7, 8, 9]
>>> g.next() # iterator is at the end; calling next again will throw
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

Надеюсь, это поможет / это то, что вы ищете.

Обновить:

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

>>> def infinite_gen():
... n = 0
... while True:
... yield n
... n = n + 1
...
>>> g = infinite_gen()
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> g.next()
3
...
python