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

Читая поваренную книгу Python в минуту и ​​глядя на генераторы. Мне трудно крутить голову.

Как я исхожу из фона Java, есть ли эквивалент Java? В книге говорилось о «Продюсер / Потребитель», однако, когда я слышу, что я думаю о нарезке.

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

  • Как связать с request_loader Flask-Login с user_loader?
  • Как получить одно значение от генератора в Python?
  • шаблон не найден, развертывание Pyramid на Webfaction
  • Как изменить объект трассировки Python при создании исключения?
  • Пересечение луча и квадрата / прямоугольника в 3D
  • Как генерировать перестановки списка без «обратных дубликатов» в Python с использованием генераторов
  • Обнаружение кругового импорта
  • Прослушать простой звуковой сигнал с помощью python без внешней библиотеки
  • 10 Solutions collect form web for “Понимание генераторов в Python”

    Примечание: этот пост предполагает синтаксис 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 инструкции 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 , поскольку они являются необходимыми инструментами для 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 

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

     >>> 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 

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

    Генераторы можно считать сокращением для создания итератора. Они ведут себя как 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, что привело к большому количеству путаницы. Вы, вероятно, имеете в виду итераторы и итерации (см. Здесь ). Затем в Python есть также функции генератора (которые возвращают объект-генератор), объекты-генераторы (которые являются итераторами) и выражения генератора (которые оцениваются объектом-генератором).

    Согласно http://docs.python.org/glossary.html#term-generator, кажется, что официальная терминология теперь заключается в том, что генератор не подходит для «функции генератора». В прошлом документация определяла термины непоследовательно, но, к счастью, это было исправлено.

    Возможно, было бы хорошей идеей быть точным и избегать термина «генератор» без дополнительной спецификации.

    Нет эквивалента Java.

    Вот немного надуманного примера:

     #! /usr/bin/python def mygen(n): x = 0 while x < n: x = x + 1 if x % 3 == 0: yield x for a in mygen(100): print a 

    В генераторе есть цикл, который работает от 0 до n, и если переменная цикла кратно 3, она дает переменную.

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

    Единственное, что я могу добавить к ответу Stephan202, – это рекомендация, чтобы вы взглянули на презентацию PyCon '08 Дэвида Бизли '08 «Генерирующие трюки для системных программистов», которая является лучшим единственным объяснением того, как и почему генераторы, которые я видел в любом месте. Это то, что взяло меня из «Python выглядит весело» на «Это то, что я искал». Он находится по адресу http://www.dabeaz.com/generators/ .

    Это помогает сделать четкое различие между функцией foo и генератором foo (n):

     def foo(n): yield n yield n+1 

    foo – функция. foo (6) – объект-генератор.

    Типичный способ использования объекта генератора находится в цикле:

     for n in foo(6): print(n) 

    Печать в петлях

     # 6 # 7 

    Подумайте о генераторе как о возобновляемой функции.

    yield ведет себя как return в том смысле, что полученные значения получают «возврат» генератором. В отличие от возврата, однако, в следующий раз, когда генератору будет предложено значение, функция генератора, foo, возобновится там, где она была остановлена ​​- после последней инструкции yield – и продолжает работать до тех пор, пока она не попадет в другую инструкцию yield.

    За кулисами, когда вы вызываете bar=foo(6) панель объектов генератора определена для вас, чтобы иметь next атрибут.

    Вы можете вызвать его самостоятельно, чтобы получить значения, полученные из foo:

     next(bar) # works in python2.6 or python3.x bar.next() # works in python2.5+, but is deprecated. Use next() if possible. 

    Когда foo заканчивается (и больше нет значений), вызов next(bar) вызывает ошибку StopInteration.

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

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

    Когда вы вызываете функцию, текущая точка выполнения («счетчик программ» или эквивалент) помещается в стек, и создается новый стек стека. Затем выполнение переносится в начало вызываемой функции.

    С регулярными функциями в какой-то момент функция возвращает значение, а стек «всплывает». Фрейм стека функции отбрасывается, и выполнение возобновляется в предыдущем месте.

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

    До Python 2.5 это были все генераторы. Python 2.5 добавил возможность передавать значения обратно в генератор. При этом переданное значение доступно в виде выражения, полученного в результате инструкции yield, которая временно возвращала управление (и значение) из генератора.

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

    Я считаю, что первое появление итераторов и генераторов было на языке программирования Icon, около 20 лет назад.

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

    После прочтения всего нескольких параграфов полезность генераторов и итераторов может стать более очевидной.

    Это сообщение будет использовать числа Фибоначчи в качестве инструмента для объяснения полезности генераторов Python .

    Этот пост будет содержать как C ++, так и код Python.

    Числа Фибоначчи определяются как последовательность: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ….

    Или вообще:

     F0 = 0 F1 = 1 Fn = Fn-1 + Fn-2 

    Это можно легко перевести в C ++-функцию:

     size_t Fib(size_t n) { //Fib(0) = 0 if(n == 0) return 0; //Fib(1) = 1 if(n == 1) return 1; //Fib(N) = Fib(N-2) + Fib(N-1) return Fib(n-2) + Fib(n-1); } 

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

    Например: Fib(3) = Fib(2) + Fib(1) , но Fib(2) также пересчитывает Fib(1) . Чем выше значение, которое вы хотите рассчитать, тем хуже будет.

    Поэтому может возникнуть соблазн переписать вышеупомянутое, отслеживая состояние в main .

     //Not supported for the first 2 elements of Fib size_t GetNextFib(size_t &pp, size_t &p) { int result = pp + p; pp = p; p = result; return result; } int main(int argc, char *argv[]) { size_t pp = 0; size_t p = 1; std::cout << "0 " << "1 "; for(size_t i = 0; i <= 4; ++i) { size_t fibI = GetNextFib(pp, p); std::cout << fibI << " "; } return 0; } 

    Но это очень уродливо, и это усложняет нашу логику в main , было бы лучше не беспокоиться о состоянии в нашей main функции.

    Мы могли бы вернуть vector значений и использовать iterator для итерации по этому набору значений, но для этого требуется много памяти сразу для большого количества возвращаемых значений.

    Итак, вернемся к нашему старому подходу, что произойдет, если мы хотим сделать что-то еще, кроме печати чисел? Нам придется копировать и вставлять весь блок кода в main и изменять выходные операторы во все, что мы хотели сделать. И если вы скопируете и вставляете код, вам следует снять его. Вы не хотите, чтобы вас застрелили?

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

     void GetFibNumbers(size_t max, void(*FoundNewFibCallback)(size_t)) { if(max-- == 0) return; FoundNewFibCallback(0); if(max-- == 0) return; FoundNewFibCallback(1); size_t pp = 0; size_t p = 1; for(;;) { if(max-- == 0) return; int result = pp + p; pp = p; p = result; FoundNewFibCallback(result); } } void foundNewFib(size_t fibI) { std::cout << fibI << " "; } int main(int argc, char *argv[]) { GetFibNumbers(6, foundNewFib); return 0; } 

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

    Но это все еще не идеально. Что, если вы хотите получить только первые 2 числа Фибоначчи, а затем сделать что-то, затем получить еще несколько, а затем сделать что-то еще.

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

    Мы могли бы реализовать модель производителя и потребителя через пару потоков. Но это еще больше усложняет код.

    Вместо этого поговорим о генераторах.

    Python имеет очень приятную языковую функцию, которая решает такие проблемы, как эти генераторы.

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

    Рассмотрим следующий код, который использует генератор:

     def fib(): pp, p = 0, 1 while 1: yield pp pp, p = p, pp+p g = fib() for i in range(6): g.next() 

    Что дает нам результаты:

    0
    1
    1
    2
    3
    5

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

    Это намного более чисто, чем код функции обратного вызова. У нас есть более чистый код, меньший код и не говоря уже о гораздо более функциональном коде (Python допускает сколь угодно большие целые числа).

    Источник

    Python - лучший язык программирования в мире.