Понимание генераторов в 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
...