Примечание: этот пост предполагает синтаксис 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()
вызов, .next
поэтому вам не нужно использовать следующее в 2.7:
>>> g = (n for n in range(3, 5))
>>> g.next()
3
send
данных к генератору. Как только вы это сделаете, у вас будет «сопрограмма». Реализовать такие шаблоны, как упомянутый Consumer / Producer, с сопрограммами очень просто, потому что им не нужныLock
s и, следовательно, они не могут зайти в тупик. Трудно описать сопрограммы без разбивки потоков, поэтому я просто скажу, что сопрограммы - очень элегантная альтернатива многопоточности.Генератор - это, по сути, функция, которая возвращает (данные) до ее завершения, но в этот момент она останавливается, и вы можете возобновить функцию в этой точке.
и так далее. (Или одно) преимущество генераторов заключается в том, что, поскольку они работают с данными по одному фрагменту за раз, вы можете работать с большими объемами данных; со списками, чрезмерные требования к памяти могут стать проблемой. Генераторы, как и списки, являются итеративными, поэтому их можно использовать одинаково:
Обратите внимание, что генераторы предоставляют другой способ работы с бесконечностью, например
Генератор инкапсулирует бесконечный цикл, но это не проблема, потому что вы получаете каждый ответ каждый раз, когда просите его.
источник
Прежде всего, термин « генератор» изначально был несколько нечетким в Python, что приводило к путанице. Вы, вероятно, имеете в виду итераторы и итерации (см. Здесь ). Затем в Python есть также функции генератора (которые возвращают объект генератора), объекты генератора (которые являются итераторами) и выражения генератора (которые оцениваются для объекта генератора).
Согласно записи глоссария для генератора, кажется, что официальная терминология теперь такова, что генератор сокращён от «функции генератора». Раньше в документации непоследовательно определялись термины, но, к счастью, это было исправлено.
Это может быть хорошей идеей, чтобы быть точным и избегать термина «генератор» без дальнейшего уточнения.
источник
Генераторы можно рассматривать как сокращение для создания итератора. Они ведут себя как итератор Java. Пример:
Надеюсь, что это помогает / это то, что вы ищете.
Обновить:
Как показывают многие другие ответы, существуют разные способы создания генератора. Вы можете использовать синтаксис скобок, как в моем примере выше, или вы можете использовать yield. Еще одна интересная особенность заключается в том, что генераторы могут быть «бесконечными» - итераторы, которые не останавливаются:
источник
Stream
s, которые гораздо больше похожи на генераторы, за исключением того, что вы, очевидно, не можете просто получить следующий элемент без удивительного количества хлопот.Нет эквивалента Java.
Вот немного надуманного примера:
В генераторе есть цикл, который работает от 0 до n, и если переменная цикла кратна 3, она возвращает переменную.
Во время каждой итерации
for
цикла выполняется генератор. Если это первый раз, когда генератор запускается, он запускается с начала, в противном случае он продолжается с предыдущего раза, когда он дал.источник
print "hello"
послеx=x+1
в моем примере, «hello» было бы напечатано 100 раз, в то время как тело цикла for было бы выполнено только 33 раза.Мне нравится описывать генераторы для тех, кто имеет хороший опыт работы с языками программирования и вычислениями, в терминах стековых фреймов.
Во многих языках есть стек, поверх которого находится текущий фрейм стека. Кадр стека включает пространство, выделенное для переменных, локальных для функции, включая аргументы, переданные этой функции.
Когда вы вызываете функцию, текущая точка выполнения («программный счетчик» или эквивалент) помещается в стек, и создается новый кадр стека. Затем выполнение переходит к началу вызываемой функции.
С обычными функциями в какой-то момент функция возвращает значение, и стек «выталкивается». Кадр стека функции отбрасывается, и выполнение возобновляется в предыдущем месте.
Когда функция является генератором, она может вернуть значение без отбрасывания кадра стека, используя оператор yield. Значения локальных переменных и счетчик программы внутри функции сохраняются. Это позволяет генератору возобновить работу позднее, продолжив выполнение из оператора yield, и он может выполнить больше кода и вернуть другое значение.
До Python 2.5 это были все генераторы. В Python 2.5 добавлена возможность передачи значений обратно в генератор. При этом переданное значение доступно в виде выражения, полученного из оператора yield, который временно вернул управление (и значение) из генератора.
Основное преимущество генераторов состоит в том, что «состояние» функции сохраняется, в отличие от обычных функций, где каждый раз, когда кадр стека отбрасывается, вы теряете все это «состояние». Второе преимущество заключается в том, что некоторые из издержек вызова функции (создание и удаление стековых фреймов) исключаются, хотя обычно это незначительное преимущество.
источник
Это помогает провести четкое различие между функцией foo и генератором foo (n):
фу это функция. foo (6) является объектом-генератором.
Типичный способ использовать объект генератора в цикле:
Петля печатает
Думайте о генераторе как о возобновляемой функции.
yield
ведет себя какreturn
в том смысле, что получаемые значения «возвращаются» генератором. Однако, в отличие от return, в следующий раз, когда у генератора запрашивается значение, функция генератора foo возобновляет работу с того места, на котором остановилась - после последнего оператора yield - и продолжает работать, пока не достигнет другого оператора yield.За кулисами, когда вы вызываете
bar=foo(6)
генератор, панель объектов определяется для вас, чтобы иметьnext
атрибут.Вы можете вызвать его самостоятельно, чтобы получить значения, полученные из foo:
Когда foo заканчивается (и больше нет значений), вызов
next(bar)
вызывает ошибку StopInstruction.источник
Единственное, что я могу добавить к ответу Stephan202 - это рекомендация взглянуть на презентацию Дэвида Бизли PyCon '08 «Уловки генераторов для системных программистов», которая является лучшим объяснением того, как и почему генераторов, которые я видел, я видел. везде. Это то, что привело меня от «Python выглядит довольно забавно» к «Это то, что я искал». Это на http://www.dabeaz.com/generators/ .
источник
Этот пост будет использовать числа Фибоначчи как инструмент для объяснения полезности генераторов Python .
Этот пост будет содержать как код C ++, так и код Python.
Числа Фибоначчи определяются как последовательность: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ....
Или вообще:
Это может быть легко передано в функцию C ++:
Но если вы хотите напечатать первые шесть чисел Фибоначчи, вы будете пересчитывать многие значения с помощью вышеуказанной функции.
Например:,
Fib(3) = Fib(2) + Fib(1)
ноFib(2)
также пересчитываетFib(1)
. Чем выше значение, которое вы хотите рассчитать, тем хуже для вас будет.Поэтому можно поддаться искушению переписать вышесказанное, отслеживая состояние в
main
.Но это очень некрасиво и усложняет нашу логику
main
. Было бы лучше не беспокоиться о состоянии нашейmain
функции.Мы могли бы возвращать
vector
значения a и использоватьiterator
итерацию для этого набора значений, но это требует много памяти сразу для большого количества возвращаемых значений.Итак, вернемся к нашему старому подходу, что произойдет, если мы захотим сделать что-то еще, кроме печати чисел? Мы должны были бы скопировать и вставить весь блок кода
main
и изменить выходные операторы так, как нам хотелось бы. А если вы копируете и вставляете код, то вас должны застрелить. Вы не хотите, чтобы вас подстрелили?Чтобы решить эти проблемы и избежать попадания в цель, мы можем переписать этот блок кода с помощью функции обратного вызова. Каждый раз, когда встречается новый номер Фибоначчи, мы вызываем функцию обратного вызова.
Это явно улучшение, ваша логика
main
не так загромождена, и вы можете делать все что угодно с числами Фибоначчи, просто определяя новые обратные вызовы.Но это все еще не идеально. Что, если вы хотите получить только первые два числа Фибоначчи, а затем что-то сделать, затем получить еще немного, а затем сделать что-то еще?
Ну, мы могли бы продолжать, как мы, и мы могли бы начать добавлять состояние снова
main
, позволяя GetFibNumbers начинаться с произвольной точки. Но это еще больше раздувает наш код, и он уже выглядит слишком большим для такой простой задачи, как печать чисел Фибоначчи.Мы могли бы реализовать модель производителя и потребителя через пару потоков. Но это усложняет код еще больше.
Вместо этого давайте поговорим о генераторах.
В Python есть очень хорошая языковая функция, которая решает такие проблемы, как эти, называемые генераторами.
Генератор позволяет вам выполнить функцию, остановиться в произвольной точке, а затем продолжить снова, где вы остановились. Каждый раз возвращая значение.
Рассмотрим следующий код, который использует генератор:
Что дает нам результаты:
Оператор
yield
используется в сочетании с генераторами Python. Сохраняет состояние функции и возвращает полученное значение. В следующий раз, когда вы вызовете функцию next () в генераторе, она продолжится там, где остановился выход.Это намного более чисто, чем код функции обратного вызова. У нас более чистый код, меньший код, и не говоря уже о гораздо более функциональном коде (Python допускает произвольно большие целые числа).
Источник
источник
Я считаю, что первое появление итераторов и генераторов было на языке программирования Icon около 20 лет назад.
Вам может понравиться обзор Icon , который позволяет вам обдумывать их, не концентрируясь на синтаксисе (поскольку Icon - это язык, который вы, вероятно, не знаете, а Грисволд объяснял преимущества своего языка людям, пришедшим с других языков).
После прочтения всего лишь нескольких абзацев полезность генераторов и итераторов может стать более очевидной.
источник
Опыт работы со списками показал, что они широко используются в Python. Однако во многих случаях использования нет необходимости создавать полный список в памяти. Вместо этого им нужно только перебирать элементы по одному.
Например, следующий код суммирования создаст полный список квадратов в памяти, итерирует по этим значениям и, когда ссылка больше не нужна, удаляет список:
sum([x*x for x in range(10)])
Память сохраняется с помощью выражения генератора вместо:
sum(x*x for x in range(10))
Аналогичные преимущества предоставляются конструкторам для контейнерных объектов:
Выражения генератора особенно полезны с такими функциями, как sum (), min () и max (), которые сводят повторяемый ввод к одному значению:
Больше
источник
Я поднял этот фрагмент кода, который объясняет 3 ключевых понятия о генераторах:
источник