it-swarm.com.ru

Что делает ключевое слово yield?

Какая польза от ключевого слова yield в Python? Что оно делает?

Например, я пытаюсь понять этот код 1 :

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

А это звонилка

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

Что происходит при вызове метода _get_child_candidates? Список возвращен? Единственный элемент? Это называется снова? Когда последующие звонки прекратятся?


1. Этот фрагмент кода был написан Йохеном Шульцем (jrschulz), который создал отличную библиотеку Python для метрических пространств. Это ссылка на полный источник: Модуль mspace .

9208
Alex. S.

Чтобы понять, что делает yield, вы должны понимать, что такое generators. И до того, как появятся генераторы iterables.

Итерируемыми

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

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylist является повторяемым. Когда вы используете понимание списка, вы создаете список, и поэтому повторяемый:

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

Все, что вы можете использовать "for... in...", является итеративным; lists, strings, файлы ...

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

Генераторы

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

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

Это то же самое, за исключением того, что вы использовали () вместо []. НО, вы не можете выполнить for i in mygenerator второй раз, поскольку генераторы можно использовать только один раз: они вычисляют 0, затем забывают об этом и вычисляют 1, и заканчивают вычислять 4, один за другим.

Уступать

yield - это ключевое слово, которое используется как return, за исключением того, что функция вернет генератор.

>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

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

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

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

Теперь самая сложная часть:

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

Генератор считается пустым после запуска функции, но больше не достигает yield. Это может быть из-за того, что цикл закончился, или из-за того, что вы больше не удовлетворяете "if/else".


Ваш код объяснил

Генератор:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

Абонент:

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

Этот код содержит несколько умных частей:

  • Цикл повторяется в списке, но список расширяется во время итерации цикла :-) Это краткий способ пройти через все эти вложенные данные, даже если это немного опасно, так как вы можете получить бесконечный цикл. В этом случае candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) исчерпывает все значения генератора, но while продолжает создавать новые объекты генератора, которые будут генерировать значения, отличные от предыдущих, поскольку он не применяется на одном узле.

  • Метод extend() - это метод объекта списка, который ожидает итерацию и добавляет ее значения в список.

Обычно мы передаем ему список:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

Но в вашем коде он получает генератор, что хорошо, потому что:

  1. Вам не нужно читать значения дважды.
  2. У вас может быть много детей, и вы не хотите, чтобы они все хранились в памяти.

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

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

Контроль истощения генератора

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

Примечание: Для Python 3 используйте print(corner_street_atm.__next__()) или print(next(corner_street_atm))

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

Itertools, твой лучший друг

Модуль itertools содержит специальные функции для управления итерациями. Вы когда-нибудь хотели дублировать генератор? Цепочка двух генераторов? Группировать значения во вложенном списке с одной линией? Map / Zip без создания другого списка?

Тогда просто import itertools.

Пример? Давайте посмотрим возможные порядки заезда на скачки:

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 (1, 3, 4, 2),
 (1, 4, 2, 3),
 (1, 4, 3, 2),
 (2, 1, 3, 4),
 (2, 1, 4, 3),
 (2, 3, 1, 4),
 (2, 3, 4, 1),
 (2, 4, 1, 3),
 (2, 4, 3, 1),
 (3, 1, 2, 4),
 (3, 1, 4, 2),
 (3, 2, 1, 4),
 (3, 2, 4, 1),
 (3, 4, 1, 2),
 (3, 4, 2, 1),
 (4, 1, 2, 3),
 (4, 1, 3, 2),
 (4, 2, 1, 3),
 (4, 2, 3, 1),
 (4, 3, 1, 2),
 (4, 3, 2, 1)]

Понимание внутренних механизмов итерации

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

В этой статье есть больше об этом как работают циклы for .

13125
e-satis

Ярлык к Гроккингyield

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

  1. Вставьте строку result = [] в начале функции.
  2. Замените каждый yield expr на result.append(expr).
  3. Вставьте строку return result внизу функции.
  4. Yay - не более yield заявлений! Прочитайте и выясните код.
  5. Сравните функцию с оригинальным определением.

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

Не путайте ваши итерации, итераторы и генераторы

Во-первых, протокол итератора - когда вы пишете

for x in mylist:
    ...loop body...

Python выполняет следующие два шага:

  1. Получает итератор для mylist:

    Вызов iter(mylist) -> возвращает объект с методом next() (или __next__() в Python 3).

    [Это шаг, о котором большинство людей забывают рассказать вам]

  2. Использует итератор для зацикливания элементов:

    Продолжайте вызывать метод next() на итераторе, возвращаемом с шага 1. Возвращаемое значение из next() присваивается x, и выполняется тело цикла. Если исключение StopIteration вызывается из next(), это означает, что в итераторе больше нет значений и цикл завершается.

Правда в том, что Python выполняет два вышеупомянутых шага в любое время, когда он хочет зацикливание содержимого объекта - так что это может быть цикл for, но это также может быть код, подобный otherlist.extend(mylist) (где otherlist - список Python ).

Здесь mylist является итерируемым, потому что он реализует протокол итератора. В пользовательском классе вы можете реализовать метод __iter__(), чтобы сделать экземпляры вашего класса итеративными. Этот метод должен возвращать итератор. Итератор - это объект с методом next(). Можно реализовать как __iter__(), так и next() в одном и том же классе и иметь __iter__() return self. Это будет работать для простых случаев, но не когда вы хотите, чтобы два итератора циклически обрабатывали один и тот же объект одновременно.

Так что это протокол итератора, многие объекты реализуют этот протокол:

  1. Встроенные списки, словари, кортежи, наборы, файлы.
  2. Пользовательские классы, которые реализуют __iter__().
  3. Генераторы.

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

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Вместо операторов yield, если у вас есть три оператора return в f123(), будет выполнена только первая, и функция завершится. Но f123() это не обычная функция. Когда вызывается f123(), он не возвращает любое из значений в операторах yield! Возвращает объект генератора. Кроме того, функция на самом деле не выходит - она ​​переходит в состояние ожидания. Когда цикл for пытается зациклить объект-генератор, функция возвращается из своего приостановленного состояния на самой следующей строке после того, как yield, из которого она ранее вернулась, выполняет следующую строку кода, в данном случае инструкцию yield, и возвращает ее как следующий пункт. Это происходит до тех пор, пока не выйдет функция, после чего генератор вызывает StopIteration и цикл завершается. 

Таким образом, объект генератора похож на адаптер - на одном конце он демонстрирует протокол итератора, предоставляя методы __iter__() и next() для поддержания цикла for счастливым. На другом конце, однако, он запускает функцию, достаточную для получения следующего значения, и переводит ее обратно в режим ожидания.

Зачем использовать генераторы?

Обычно вы можете написать код, который не использует генераторы, но реализует ту же логику. Одним из вариантов является использование временного списка «трюк», о котором я упоминал ранее. Это не будет работать во всех случаях, например, если у вас бесконечные циклы, или это может привести к неэффективному использованию памяти, когда у вас действительно длинный список. Другой подход заключается в реализации нового итерируемого класса SomethingIter, который сохраняет состояние в элементах экземпляра и выполняет следующий логический шаг в своем методе next() (или __next__() в Python 3). В зависимости от логики код внутри метода next() может выглядеть очень сложным и быть подверженным ошибкам. Здесь генераторы обеспечивают чистое и простое решение.

1751
user28409

Думайте об этом так:

Итератор - это просто причудливый термин для объекта, у которого есть метод next (). Таким образом, функция yield-ed в итоге выглядит примерно так:

Оригинальная версия:

def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

Это в основном то, что интерпретатор Python делает с приведенным выше кодом:

class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i

Чтобы лучше понять, что происходит за кулисами, цикл for можно переписать так:

iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

Это имеет больше смысла или просто сбивает вас с толку? :)

Я должен отметить, что это это упрощение в иллюстративных целях. :)

445
Jason Baker

Ключевое слово yield сводится к двум простым фактам:

  1. Если компилятор обнаруживает ключевое слово yieldгде угодно внутри функции, эта функция больше не возвращается через оператор return. Вместо, он сразу возвращает ленивый объект «отложенного списка», называемый генератором
  2. Генератор повторяем. Что такое повторяемый? Это что-то вроде list или set или range или dict-view, с встроенный протокол для посещения каждого элемента в определенном порядке.

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

generator = myYieldingFunction(...)
x = list(generator)

   generator
       v
[x[0], ..., ???]

         generator
             v
[x[0], x[1], ..., ???]

               generator
                   v
[x[0], x[1], x[2], ..., ???]

                       StopIteration exception
[x[0], x[1], x[2]]     done

list==[x[0], x[1], x[2]]

Пример

Давайте определим функцию makeRange, которая похожа на функцию range в Python. Вызов makeRange(n) ВОЗВРАЩАЕТ ГЕНЕРАТОРА:

def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

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

>>> list(makeRange(5))
[0, 1, 2, 3, 4]

Сравнение примера с «просто возвратом списка»

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

# list-version                   #  # generator-version
def makeRange(n):                #  def makeRange(n):
    """return [0,1,2,...,n-1]""" #~     """return 0,1,2,...,n-1"""
    TO_RETURN = []               #>
    i = 0                        #      i = 0
    while i < n:                 #      while i < n:
        TO_RETURN += [i]         #~         yield i
        i += 1                   #          i += 1  ## indented
    return TO_RETURN             #>

>>> makeRange(5)
[0, 1, 2, 3, 4]

Есть одно существенное отличие, хотя; смотрите последний раздел.


Как вы можете использовать генераторы

Итерируемое является последней частью понимания списка, и все генераторы являются итеративными, поэтому они часто используются так:

#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

Чтобы лучше понять генераторы, вы можете поиграться с модулем itertools (обязательно используйте chain.from_iterable вместо chain при наличии гарантии). Например, вы можете даже использовать генераторы для реализации бесконечно длинных ленивых списков, таких как itertools.count(). Вы можете реализовать свою собственную функцию def enumerate(iterable): Zip(count(), iterable) или, альтернативно, сделать это с помощью ключевого слова yield в цикле while.

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


За кулисами

Вот как работает «Протокол итерации Python». То есть то, что происходит, когда вы делаете list(makeRange(5)). Это то, что я описываю ранее как «ленивый, добавочный список».

>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

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


Мелочи

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

В языке Python итерируемый - это любой объект, который «понимает концепцию цикла for», например, список [1,2,3], а итератор является конкретным экземпляром запрошенного for- цикл, как [1,2,3].__iter__(). Генератор точно такой же, как и любой итератор, за исключением того, как он был написан (с синтаксисом функции).

Когда вы запрашиваете итератор из списка, он создает новый итератор. Однако, когда вы запрашиваете итератор у итератора (что вы редко делаете), он просто дает вам свою копию.

Таким образом, в маловероятном случае, если вы не в состоянии сделать что-то подобное ...

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

... потом помните, что генератор - это итератор; то есть одноразовое использование. Если вы хотите использовать его повторно, вам следует снова вызвать myRange(...). Если вам нужно использовать результат дважды, преобразуйте результат в список и сохраните его в переменной x = list(myRange(5)). Те, кому абсолютно необходимо клонировать генератор (например, кто выполняет ужасно хакерское метапрограммирование), могут использовать itertools.tee , если это абсолютно необходимо, поскольку копируемый итератор Python PEP предложение по стандартам было отложено.

380
ninjagecko

Что делает ключевое слово yield в Python?

Схема ответа/Резюме

  • Функция с yield , при вызове возвращает Generator .
  • Генераторы являются итераторами, потому что они реализуют протокол итератора , так что вы можете перебирать их.
  • Генератором также может быть отправленная информация, что делает его концептуально сопрограммой.
  • В Python 3 вы можете делегироватьот одного генератора другому в обоих направлениях с помощью yield from.
  • (Приложение критикует пару ответов, включая самый верхний, и обсуждает использование return в генераторе.)

Генераторы:

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

Идея для генераторов исходит из других языков (см. Сноску 1) с различными реализациями. В генераторах Python выполнение кода frozen в точке выхода. Когда вызывается генератор (методы обсуждаются ниже), выполнение возобновляется, а затем останавливается при следующем выходе.

yield предоставляет простой способ реализации протокола итератора , определяемый следующими двумя методами: __iter__ и nextPython 2) или __next__ (Python 3). Оба эти метода Делают объект итератором, который можно проверить типом с помощью класса Iterator Abstract Base Из модуля collections.

>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

Тип генератора является подтипом итератора:

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

И при необходимости мы можем проверить тип так:

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

Особенность Iteratorзаключается в том, что после исчерпания вы не можете повторно использовать или сбросить его:

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

Вам придется сделать еще один, если вы хотите снова использовать его функциональность (см. Сноску 2):

>>> list(func())
['I am', 'a generator!']

Можно получить данные программно, например:

def func(an_iterable):
    for item in an_iterable:
        yield item

Вышеупомянутый простой генератор также эквивалентен приведенному ниже - начиная с Python 3.3 (и недоступен в Python 2), вы можете использовать yield from :

def func(an_iterable):
    yield from an_iterable

Тем не менее, yield from также позволяет делегировать субгенераторам , Что будет объяснено в следующем разделе о совместном делегировании с субпрограммами.

Сопрограммы:

yield формирует выражение, которое позволяет отправлять данные в генератор (см. сноску 3)

Вот пример, обратите внимание на переменную received, которая будет указывать на данные, отправляемые генератору:

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

Во-первых, мы должны поставить генератор в очередь с помощью встроенной функции next . Он Вызовет соответствующий метод next или __next__, в зависимости от используемой вами версии Python:

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

И теперь мы можем отправлять данные в генератор. ( Отправка None - это Так же, как вызов next .):

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

Совместное делегирование в суб-сопрограмму с yield from

Теперь напомним, что yield from доступен в Python 3. Это позволяет нам делегировать Сопрограммы для подгруппы:

def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

И теперь мы можем делегировать функциональность суб-генератору, и он может использоваться Генератором так же, как и выше:

>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

Вы можете прочитать больше о точной семантике yield from в PEP 380.

Другие методы: закрыть и бросить

Метод close вызывает GeneratorExit в тот момент, когда выполнение функции Было заморожено. Это также будет вызываться __del__, чтобы вы Могли поместить любой код очистки, где вы обрабатываете GeneratorExit:

>>> my_account.close()

Вы также можете выдать исключение, которое может быть обработано в генераторе Или передано обратно пользователю:

>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

Заключение

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

_ (Что делает ключевое слово yield в Python?

Оказывается, yield много делает. Я уверен, что мог бы добавить еще более Подробных примеров к этому. Если вы хотите большего или имеете конструктивную критику, дайте мне знать, комментируя Ниже.


Приложение:

Критика топ/принятого ответа **

  • Он запутался в том, что делает iterable, просто используя список в качестве примера. См. Мои ссылки выше, но в итоге: итерируемый имеет метод __iter__, возвращающий iterator. An iteratorпредоставляет метод .nextPython 2 или .__next__ (Python 3), который неявно вызывается циклами for до тех пор, пока он не вызовет StopIteration, и как только он это сделает, он продолжит это делать.
  • Затем он использует выражение генератора, чтобы описать, что такое генератор. Поскольку генератор - это просто удобный способ создания _ (итератор, это только запутывает вопрос, а мы до сих пор не дошли до части yield).
  • В Контроль исчерпания генератораон вызывает метод .next, когда вместо этого ему следует использовать встроенную функцию next. Это будет соответствующий уровень косвенности, поскольку его код не работает в Python 3.
  • Itertools? Это не имело отношения к тому, что yield делает вообще.
  • Нет обсуждения методов, которые yield предоставляет вместе с новой функциональностью yield from в Python 3. Верхний/принятый ответ - очень неполный ответ.

Критика ответа с предложением yield в выражении или понимании генератора.

В настоящее время грамматика допускает любое выражение в понимании списка. 

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

Так как yield является выражением, некоторые считают его интересным для использования в пониманиях или выражениях-генераторах, несмотря на то, что он не привел ни одного особенно хорошего варианта использования.

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

30 января 2017 года в 19:05 Бретт Кэннон написал:

В воскресенье, 29 января 2017 года в 16:39 Крейг Родригес написал:

Я в порядке с любым подходом. Оставлять вещи такими, как они есть в Python 3 , Нехорошо, ИМХО.

Мой голос - это ошибка синтаксиса, поскольку вы не получаете того, что ожидаете от Синтаксиса.

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

С точки зрения попадания туда, мы, вероятно, захотим:

  • Синтаксическое предупреждение или устаревшее предупреждение в 3.7
  • Py3k предупреждение в 2.7.x
  • Ошибка синтаксиса в 3.8

Ура, Ник.

- Ник Коглан | ncoghlan на gmail.com | Брисбен, Австралия

Кроме того, существует нерешенная проблема (10544) , которая, кажется, указывает на то, что never является хорошей идеей (PyPy, реализация Python, написанная на Python, уже вызывает предупреждения синтаксиса .)

В итоге, пока разработчики CPython не скажут нам иначе: Не помещайте yield в выражение или понимание генератора.

Оператор return в генераторе

В Python 2 :

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

expression_list - это, по сути, любое количество выражений, разделенных запятыми - по сути, в Python 2 вы можете остановить генератор с помощью return, но вы не можете вернуть значение.

В Python 3

В функции генератора оператор return указывает, что генератор завершен и вызовет вызов StopIteration. Возвращенное значение (если оно есть) используется в качестве аргумента для построения StopIteration и становится атрибутом StopIteration.value.

Сноски

  1. Языки CLU, Sather и Icon упоминались в предложении , Чтобы представить концепцию генераторов в Python. Общая идея заключается в том, что Функция может поддерживать внутреннее состояние и выдавать промежуточные Точки данных по требованию пользователя. Это обещало быть превосходящим по производительности По сравнению с другими подходами, включая потоки Python , которые даже недоступны в некоторых системах.

  2. Это означает, например, что объекты xrange (range в Python 3) не являются Iterators, даже если они итеративные, потому что их можно использовать повторно. Как и списки, их методы __iter__ возвращают объекты итератора.

  3. yield изначально была введена как оператор, то есть она могла появляться только в начале строки в блоке кода. Теперь yield создает выражение выхода. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Это изменение было предложено , чтобы позволить пользователю отправлять данные в генератор так же, как их можно получить. Чтобы отправить данные, нужно иметь возможность назначить их чему-либо, и для этого Оператор просто не будет работать.

295
Aaron Hall

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

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

list.extend вызывает итератор, пока он не исчерпан. В случае с примером кода, который вы опубликовали, было бы гораздо проще просто вернуть кортеж и добавить его в список.

260
Douglas Mayle

Есть еще одна вещь, которую стоит упомянуть: функция, которая возвращает результат, на самом деле не должна завершаться. Я написал такой код:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

Тогда я могу использовать его в другом коде, как это:

for f in fib():
    if some_condition: break
    coolfuncs(f);

Это действительно помогает упростить некоторые проблемы и облегчает работу с некоторыми вещами. 

199
Claudiu

Для тех, кто предпочитает минимальный рабочий пример, медитируйте на этой интерактивной Python сессии:

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print i
... 
1
2
3
>>> for i in g:
...   print i
... 
>>> # Note that this time nothing was printed
171
Daniel

TL; DR

Вместо этого:

def square_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

сделай это:

def square_yield(n):
    for x in range(n):
        y = x * x
        yield y                           # with this one.

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

Это был мой первый "ага" момент с доходностью.


yield является сладкий способ сказать 

построить серию вещей

Такое же поведение:

>>> for square in square_list(4):
...     print(square)
...
0
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
0
1
4
9

Разное поведение:

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

Урожай равен lazy , он откладывает вычисления. Функция с выходом на самом деле вообще не выполняется, когда вы ее вызываете. Возвращает объект итератор , который запоминает, где он остановился. Каждый раз, когда вы вызываете next() на итераторе (это происходит в цикле for), выполнение в дюймах вперед до следующего выхода. return вызывает StopIteration и завершает серию (это естественное завершение цикла for).

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

>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

Если вам нужно несколько проходов и серия не слишком длинная, просто вызовите list():

>>> list(square_yield(4))
[0, 1, 4, 9]

Блестящий выбор слова yield, потому что оба значения применяются:

урожайность - производить или предоставлять (как в сельском хозяйстве)

... предоставить следующие данные в серии.

уступить - уступить или отказаться (как в политической власти)

... отказаться от выполнения процессора, пока итератор не продвинется.

160
Bob Stein

Выход дает вам генератор. 

def get_odd_numbers(i):
    return range(1, i, 2)
def yield_odd_numbers(i):
    for x in range(1, i, 2):
       yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5

Как видите, в первом случае foo хранит весь список в памяти сразу. Это не имеет большого значения для списка из 5 элементов, но что, если вы хотите список из 5 миллионов? Мало того, что это огромный пожиратель памяти, он также требует много времени для создания во время вызова функции. Во втором случае, бар просто дает вам генератор. Генератор является итеративным - это означает, что вы можете использовать его в цикле for и т.д., Но к каждому значению можно получить доступ только один раз. Все значения также не сохраняются в памяти одновременно; объект генератора «запоминает», где он находился в цикле в последний раз, когда вы его вызывали - таким образом, если вы используете итеративный подсчет (скажем) до 50 миллиардов, вам не нужно считать до 50 миллиардов всех и запомните 50 миллиардов чисел Опять же, это довольно надуманный пример, вы, вероятно, использовали бы itertools, если бы вы действительно хотели сосчитать до 50 миллиардов. :)

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

148
RBansal

Это возвращает генератор. Я не особенно знаком с Python, но я считаю, что это то же самое, что и блоки итератора C # , если вы знакомы с ними.

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

145
Jon Skeet

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

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

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

Продолжения в этом более общем виде могут быть реализованы двумя способами. В способе call/cc стек программы буквально сохраняется, а затем, когда вызывается продолжение, стек восстанавливается.

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

def save_file(filename):
  def write_file_continuation():
    write_stuff_to_file(filename)

  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

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

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


Теперь поговорим о генераторах в Python. Генераторы - это определенный подтип продолжения. Тогда как продолжения в целом могут сохранять состояние вычисление(т. Е. Стек вызовов программы), генераторы могут сохранять состояние итерации только через итератор. Хотя это определение слегка вводит в заблуждение для определенных случаев использования генераторов. Например:

def f():
  while True:
    yield 4

Это явно разумная итерация, поведение которой четко определено - каждый раз, когда генератор повторяет ее, он возвращает 4 (и делает это всегда). Но это, вероятно, не тип прототипа итерируемого, который приходит на ум, когда мы думаем об итераторах (то есть, for x in collection: do_something(x)). Этот пример иллюстрирует мощь генераторов: если что-то является итератором, генератор может сохранить состояние своей итерации.

Повторим: продолжения могут сохранять состояние стека программы, а генераторы могут сохранять состояние итерации. Это означает, что продолжения более мощные, чем генераторы, но также и то, что генераторы намного, намного проще. Их легче реализовать для языкового дизайнера, и их легче использовать программисту (если у вас есть время, чтобы записать, попробуйте прочитать и понять эту страницу о продолжениях и вызовите/cc ).

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

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

class Generator():
  def __init__(self,iterable,generatorfun):
    self.next_continuation = lambda:generatorfun(iterable)

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

где ключевое слово yield фактически является синтаксическим сахаром для реальной функции генератора, в основном что-то вроде:

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

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

134
aestrivex

Вот пример на простом языке. Я приведу соответствие между человеческими концепциями высокого уровня и концепциями Python низкого уровня.

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

  • Я позвоню вам и скажу, что мне нужна последовательность чисел, которая производится определенным образом, и я дам вам знать, что это за алгоритм. 
    Этот шаг соответствует def в функции генератора, то есть функции, содержащей yield.
  • Некоторое время спустя я говорю вам: «Хорошо, будьте готовы рассказать мне последовательность чисел». 
    Этот шаг соответствует вызову функции генератора, которая возвращает объект генератора. Обратите внимание, что вы еще не сказали мне никаких чисел; вы просто берете свою бумагу и карандаш.
  • Я прошу вас: «скажите мне следующий номер», а вы скажите мне первый номер; после этого вы ждете, чтобы я попросил у вас следующий номер. Ваша работа - помнить, где вы были, какие цифры вы уже сказали, и какой следующий номер. Меня не волнуют детали. 
    Этот шаг соответствует вызову .next() для объекта генератора.
  • ... повторять предыдущий шаг, пока ...
  • в конце концов, вы можете прийти к концу. Вы не говорите мне номер; ты просто кричишь: "Держи лошадей! Я готов! Нет больше цифр!" 
    Этот шаг соответствует объекту генератора, завершающему свою работу и вызывающему исключение StopIteration Функция генератора не должна вызывать исключение. Он вызывается автоматически, когда функция завершается или выдает return.

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

Самым известным пользователем протокола итератора является команда for в Python. Итак, всякий раз, когда вы делаете:

for item in sequence:

не имеет значения, является ли sequence списком, строкой, словарем или генератором object как описано выше; результат тот же: вы читаете элементы из последовательности один за другим.

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

Для получения более точной информации прочитайте о типах итераторов , yield yield и generators в документации по Python.

120
tzot

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

Чтобы понять, что yield делает в следующем коде, вы можете использовать свой палец, чтобы проследить цикл по любому коду, имеющему yield. Каждый раз, когда вы нажимаете пальцем на yield, вы должны ждать ввода next или send. Когда вызывается next, вы прослеживаете код до тех пор, пока не нажмете на yield… код справа от yield будет оценен и возвращен вызывающей стороне… затем вы ждете. Когда next вызывается снова, вы выполняете еще один цикл по коду. Однако вы заметите, что в сопрограмме yield также может использоваться с send…, который будет отправлять значение из вызывающей стороны в функцию выдачи. Если задано send, тогда yield получает отправленное значение и выплевывает его в левую сторону… тогда трассировка по коду продолжается до тех пор, пока вы снова не нажмете yield (возвращая значение в конце, как если бы next вызывался).

Например:

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()
105
Mike McKerns

Существует еще одно использование и значение yield (начиная с Python 3.3):

yield from <expr>

СPEP 380 - Синтаксис для делегирования субгенератору:

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

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

Более того это введет (начиная с Python 3.5):

async def new_coroutine(data):
   ...
   await blocking_action()

чтобы не перепутать сопрограммы с обычным генератором (сегодня yield используется в обоих).

97
Sławomir Lenart

Все отличные ответы, однако немного сложны для новичков.

Я предполагаю, что вы узнали утверждение return.

По аналогии, return и yield являются близнецами. return означает «возврат и остановка», тогда как «доходность» означает «возврат, но продолжить»

  1. Попробуйте получить num_list с помощью return.
def num_list(n):
    for i in range(n):
        return i

Запустить его:

In [5]: num_list(3)
Out[5]: 0

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

  1. Приходит yield

Замените return на yield:

In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:

In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>

In [12]: list(num_list(3))
Out[12]: [0, 1, 2]

Теперь вы выиграли, чтобы получить все цифры.

По сравнению с return, который запускается один раз и останавливается, yield запускает запланированное вами время. Вы можете интерпретировать return как return one of them, а yield как return all of them. Это называется iterable.

  1. Еще один шаг, который мы можем переписать yield оператор с return
In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result

In [16]: num_list(3)
Out[16]: [0, 1, 2]

Это ядро ​​yield.

Разница между выводом списка return и выходом объекта yield:

Вы всегда будете получать [0, 1, 2] из объекта списка, но сможете получить их только из «выходных данных объекта yield» один раз. Итак, у него есть новое имя generator объект, как показано в Out[11]: <generator object num_list at 0x10327c990>.

В заключение, в качестве метафоры, чтобы понять это:

  • return и yield - близнецы
  • list и generator - близнецы
86
JawSaw

Вот несколько примеров Python о том, как на самом деле реализовать генераторы, как если бы Python не предоставил им синтаксический сахар:

Как генератор Python:

from itertools import islice

def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

Использование лексических замыканий вместо генераторов

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

Использование замыканий объектов вместо генераторов (потому что ClosuresAndObjectsAreEquivalent )

class fib_gen3:
    def __init__(self):
        self.a, self.b = 1, 1

    def __call__(self):
        r = self.a
        self.a, self.b = self.b, self.a + self.b
        return r

assert [1,1,2,3,5] == ftake(fib_gen3(), 5)
83
Dustin Getz

Я собирался опубликовать «прочитайте страницу 19« Bethonley »Python: Essential Reference» для быстрого описания генераторов », но многие другие уже опубликовали хорошие описания.

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

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

81
johnzachary

С точки зрения программирования, итераторы реализованы как thunks .

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

http://en.wikipedia.org/wiki/Message_passing

" next " - это сообщение, отправленное закрытию, созданное вызовом " iter ".

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

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

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->
77
alinsoar

Вот простой пример:

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

Результат:

loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

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

Вроде бы интересная и приятная способность: D

70
Engin OZTURK

Вот мысленный образ того, что делает yield.

Мне нравится думать о потоке как о стеке (даже если он не реализован таким образом).

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

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

Так что это своего рода замороженная функция, на которой висит генератор.

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

Сравните следующие примеры:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

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

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

Вызов yielderFunction() не запускает его код, но делает генератор из кода. (Возможно, это хорошая идея называть такие вещи префиксом yielder для удобства чтения.)

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

В полях gi_code и gi_frame хранится замороженное состояние. Изучая их с помощью dir(..), мы можем подтвердить, что наша ментальная модель выше заслуживает доверия.

58
Evgeni Sergeev

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

def getNextLines():
   while con.isOpen():
       yield con.read()

Вы можете использовать его в своем коде следующим образом:

for line in getNextLines():
    doSomeThing(line)

Выполнение управления переводом получил

Управление выполнением будет передано из getNextLines () в цикл for при выполнении yield. Таким образом, каждый раз, когда вызывается getNextLines (), выполнение начинается с того места, где оно было приостановлено в последний раз.

Таким образом, вкратце, функция со следующим кодом

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

распечатает

"first time"
"second time"
"third time"
"Now some useful value 12"
49
Mangu Singh Rajpurohit

(Мой ответ ниже говорит только с точки зрения использования генератора Python, а не базовой реализации механизма генератора , который включает в себя некоторые приемы работы со стеком и кучей.)

Когда yield используется вместо return в функции python, эта функция превращается в нечто особенное, называемое generator function. Эта функция вернет объект типа generator. Ключевое слово yield - это флаг, который уведомляет компилятор python о специальной обработке такой функции. Нормальные функции завершатся, когда из него будет возвращено некоторое значение. Но с помощью компилятора функцию генератора можно считать как возобновляемую. Таким образом, контекст выполнения будет восстановлен, и выполнение будет продолжено с последнего запуска. Пока вы явно не вызовете return, что вызовет исключение StopIteration (которое также является частью протокола итератора), или не достигнет конца функции. Я нашел много ссылок на generator, но этот one из functional programming perspective является наиболее легко усваиваемым.

(Теперь я хочу поговорить об обосновании generator и iterator, основанном на моем собственном понимании. Я надеюсь, что это поможет вам понятьсущественную мотивациюитератора и генератора. Такая концепция также отображается на других языках, таких как C #.)

Как я понимаю, когда мы хотим обработать кучу данных, мы обычно сначала храним данные где-то, а затем обрабатываем их одну за другой. Но этот наивный подход проблематичен. Если объем данных огромен, заранее хранить их в целом дорого. Таким образом, вместо непосредственного сохранения самого data, почему бы не сохранить какое-то косвенное metadata, т.е. the logic how the data is computed

Существует два подхода к переносу таких метаданных.

  1. Подход OO, мы заключаем метаданные as a class. Это так называемый iterator, который реализует протокол итератора (то есть методы __next__() и __iter__()). Это также часто встречающийся шаблон дизайна итератора .
  2. При функциональном подходе мы заключаем метаданные as a function. Это Так называемый generator function. Но под капотом возвращенный generator object по-прежнему IS-A итератор, поскольку он также реализует протокол итератора.

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

43
smwikipedia

Доходность есть объект

return в функции вернет одно значение.

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

Что еще более важно, yield является барьером .

подобно барьеру в языке CUDA, он не будет передавать управление, пока не завершит .

То есть он будет запускать код в вашей функции с самого начала, пока не достигнет yield. Затем он вернет первое значение цикла.

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

43
Kaleem Ullah

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

def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

просто выводит

one
two
three

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

Скажем, вы хотите создать свою собственную функцию range, которая производит итеративный диапазон чисел, вы можете сделать это так,

def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

и используйте это так;

for i in myRangeNaive(10):
    print i

Но это неэффективно, потому что

  • Вы создаете массив, который используете только один раз (это тратит впустую память)
  • Этот код фактически зацикливается на этом массиве дважды! :(

К счастью, Гвидо и его команда были достаточно щедры на разработку генераторов, поэтому мы могли просто сделать это;

def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return

for i in myRangeSmart(10):
    print i

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

42
redbandit

Многие люди используют return, а не yield, но в некоторых случаях yield может быть более эффективным и с ним легче работать.

Вот пример, для которого yield определенно лучше всего подходит:

возврат (в функции)

import random

def return_dates():
    dates = [] # With 'return' you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates

доходность (в функции)

def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # 'yield' makes a generator automatically which works
                   # in a similar way. This is much more efficient.

Вызов функций

dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)

dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
    print(i)

Обе функции делают одно и то же, но yield использует три строки вместо пяти и имеет на одну переменную меньше, о которой нужно беспокоиться.

Это результат из кода:

 Output

Как видите, обе функции выполняют одно и то же. Единственное отличие состоит в том, что return_dates() дает список, а yield_dates() дает генератор.

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

40
Tom Fuller

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

35
Theoremiser

Ключевое слово yield просто собирает возвращаемые результаты. Думайте о yield как return +=

35
Bahtiyar Özdere

Вот простой подход, основанный на yield, для вычисления ряда Фибоначчи:

def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b

Когда вы введете это в свой REPL, а затем попытаетесь вызвать его, вы получите загадочный результат:

>>> fib()
<generator object fib at 0x7fa38394e3b8>

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

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

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

>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

Косвенно, если вы предоставите fib в цикл for, инициализатор list, инициализатор Tuple или что-либо еще, что ожидает объект, который генерирует/производит значения, вы будете «потреблять» генератор до тех пор, пока он не сможет произвести больше значений ( и оно возвращается)

results = []
for i in fib(30):       # consumes fib
    results.append(i) 
# can also be accomplished with
results = list(fib(30)) # consumes fib

Аналогично, с инициализатором Tuple

>>> Tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)

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

Когда вы впервые вызываете fib, вызывая его:

f = fib()

Python компилирует функцию, встречает ключевое слово yield и просто возвращает объект генератора обратно к вам. Не очень полезно, кажется. 

Когда вы затем запрашиваете, он генерирует первое значение, прямо или косвенно, он выполняет все операторы, которые он находит, пока не встретит yield, затем он возвращает значение, которое вы указали для yield, и делает паузу. Для примера, который лучше демонстрирует это, давайте использовать некоторые вызовы print (замените на print "text", если на Python 2):

def yielder(value):
    """ This is an infinite generator. Only use next on it """ 
    while 1:
        print("I'm going to generate the value for you")
        print("Then I'll pause for a while")
        yield value
        print("Let's go through it again.")

Теперь введите в REPL:

>>> gen = yielder("Hello, yield!")

у вас есть объект генератора, ожидающий команду для создания значения. Используйте next и посмотрите, что напечатано:

>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

Результаты без кавычек - то, что напечатано. Результатом в кавычках является то, что возвращается из yield. Звоните next снова сейчас:

>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

Генератор запоминает, что он был приостановлен на yield value, и возобновляется оттуда. Следующее сообщение печатается, и поиск оператора yield для приостановки выполняется снова (из-за цикла while).

32
Jim Fasarakis Hilliard

Простой пример того, что легко объяснить: yield

def f123():
    for _ in range(4):
        yield 1
        yield 2


for i in f123():
    print i

Результат: 

1 2 1 2 1 2 1 2
29
Gavriel Cohen

Еще один TL; DR

Итератор в списке : next() возвращает следующий элемент списка

Генератор итераторов : next() будет вычислять следующий элемент на лету (выполнить код)

Вы можете увидеть yield/generator как способ вручную запустить control flow извне (например, продолжить цикл на один шаг), вызвав next, сколь бы сложным он ни был.

Примечание : ГенераторНЕнормальная функция. Он запоминает предыдущее состояние как локальные переменные (стек). Смотрите другие ответы или статьи для подробного объяснения. Генератор может быть повторен только один раз . Вы можете обойтись без yield, но это будет не так хорошо, так что это можно считать «очень хорошим» языком сахара.

28
Christophe Roussy

доходность аналогична доходности. Разница в следующем: 

yield делает функцию итеративной (в следующем примере функция primes(n = 1) становится итеративной).
По сути, это означает, что при следующем вызове функции она будет продолжаться с того места, где она вышла (после строки yield expression).

def isprime(n):
    if n == 1:
        return False
    for x in range(2, n):
        if n % x == 0:
            return False
    else:
        return True

def primes(n = 1):
   while(True):
       if isprime(n): yield n
       n += 1 

for n in primes():
    if n > 100: break
    print(n)

В приведенном выше примере, если isprime(n) истина, он вернет простое число. На следующей итерации будет продолжено со следующей строки 

n += 1  
24
blueray

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

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

Ваш код пересекает структуру двоичного дерева. Давайте возьмем это дерево для примера:

    5
   / \
  3   6
 / \   \
1   4   8

И еще одна более простая реализация обхода дерева бинарного поиска:

class Node(object):
..
def __iter__(self):
    if self.has_left_child():
        for child in self.left:
            yield child

    yield self.val

    if self.has_right_child():
        for child in self.right:
            yield child

Код выполнения находится в объекте Tree, который реализует __iter__ так:

def __iter__(self):

    class EmptyIter():
        def next(self):
            raise StopIteration

    if self.root:
        return self.root.__iter__()
    return EmptyIter()

Оператор while candidates можно заменить на for element in tree; Python перевести это на

it = iter(TreeObj)  # returns iter(self.root) which calls self.root.__iter__()
for element in it: 
    .. process element .. 

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

  1. корневой элемент является первым; проверьте, оставили ли он дочерние элементы, и for итерируйте их (давайте назовем его it1, потому что это первый объект итератора)
  2. у него есть дочерний элемент, поэтому for выполняется. for child in self.left создает новый итератор из self.left, который сам является объектом Node (it2)
  3. Та же логика, что и у 2, и создается новая iterator (it3)
  4. Теперь мы достигли левого конца дерева. У it3 нет левых потомков, поэтому он продолжается и yield self.value
  5. При следующем вызове next(it3) он вызывает StopIteration и существует, так как не имеет правых потомков (доходит до конца функции, ничего не возвращая)
  6. it1 и it2 по-прежнему активны - они не исчерпаны, и вызов next(it2) даст значения, а не вызовет StopIteration
  7. Теперь мы вернулись в контекст it2 и вызываем функцию next(it2), которая продолжается там, где она остановилась: сразу после оператора yield child. Поскольку у него больше нет левых потомков, он продолжается и выдает self.val.

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

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

Я надеюсь, что это немного способствовало этой легендарной теме. Я потратил несколько часов на то, чтобы понять этот процесс.

10
Chen A.

Короче говоря, использование yield аналогично ключевому слову return , за исключением того, что оно возвращает generator .
A генератор объект перемещается только один раз .

yield имеет два преимущества: 

  1. Вам не нужно читать эти значения дважды; 
  2. Вы можете получить много дочерних узлов, не помещая их все в память.
8
123

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

Другая замечательная вещь, которую делает ключевое слово yield - это сохранение state функции генератора

Таким образом, мы можем установить number в другое значение каждый раз, когда generator дает. 

Вот пример:

def getPrimes(number):
    while True:
        if isPrime(number):
            number = yield number     # a miracle occurs here
        number += 1

def printSuccessivePrimes(iterations, base=10):
primeGenerator = getPrimes(base)
primeGenerator.send(None)
for power in range(iterations):
    print(primeGenerator.send(base ** power))
7
ARGeo

Уступать

>>> def create_generator():
...    my_list = range(3)
...    for i in my_list:
...        yield i*i
...
>>> my_generator = create_generator() # create a generator
>>> print(my_generator) # my_generator is an object!
<generator object create_generator at 0xb7555c34>
>>> for i in my_generator:
...     print(i)
0
1
4

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

6
Gavriel Cohen

Аналогия может помочь понять идею здесь:

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

Генераторы Python не сильно отличаются от этой концепции.

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

Код машины:

def barcode_generator():
    serial_number = 10000  # Initial barcode
    while True:
        yield serial_number
        serial_number += 1


barcode = barcode_generator()
while True:
    number_of_lightbulbs_to_generate = int(input("How many lightbulbs to generate? "))
    barcodes = [next(barcode) for _ in range(number_of_lightbulbs_to_generate)]
    print(barcodes)

    # function_to_create_the_next_batch_of_lightbulbs(barcodes)

    produce_more = input("Produce more? [Y/n]: ")
    if produce_more == "n":
        break

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

Результат:

How many lightbulbs to generate? 5
[10000, 10001, 10002, 10003, 10004]
Produce more? [Y/n]: y
How many lightbulbs to generate? 6
[10005, 10006, 10007, 10008, 10009, 10010]
Produce more? [Y/n]: y
How many lightbulbs to generate? 7
[10011, 10012, 10013, 10014, 10015, 10016, 10017]
Produce more? [Y/n]: n
6
Rafael

yield Тип генератора, который можно использовать в python.

вот ссылка, чтобы увидеть, что действительно делает Yield, также в поколении. Генераторы и ключевое слово Yield - Python Central (PC)

Также yield работает как return, но не так, как return. Даже есть ссылка, которая объясняет yield больше, если вы не очень хорошо понимаете другую. Улучши свой навык доходности - jeffknupp

3
PIZZZZZZZZZZZA is here

Проще говоря, «yield» похож на «возвращение» значения, но он работает в Генераторе.

1
user3701435

В простом yield возвращает объект-генератор вместо значений. 

Ниже простой пример поможет!

def sim_generator():
    for i in range(3):
        yield(i)

obj = sim_generator()
next(obj) # another way is obj.__next__()
next(obj)
next(obj)

приведенный выше код возвращает 0, 1, 2

или даже короткий

for val in sim_generator():
    print(val)

вернуть 0, 1, 2

Надеюсь это поможет

1
Vivek Ananthan

Простая функция генератора

def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n

оператор yield приостанавливает функцию, сохраняя все ее состояния, а затем продолжает последовательные вызовы.

https://www.programiz.com/python-programming/generator

0
Savai Maheshwari

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

In [4]: def make_cake(numbers):
   ...:     for i in range(numbers):
   ...:         yield 'Cake {}'.format(i)
   ...:

In [5]: factory = make_cake(5)

Здесь factory называется генератор, который делает вас тортов. Если вы вызываете make_function, вы получаете генератор вместо запуска этой функции. Это потому, что когда ключевое слово yield присутствует в функции, оно становится генератором.

In [7]: next(factory)
Out[7]: 'Cake 0'

In [8]: next(factory)
Out[8]: 'Cake 1'

In [9]: next(factory)
Out[9]: 'Cake 2'

In [10]: next(factory)
Out[10]: 'Cake 3'

In [11]: next(factory)
Out[11]: 'Cake 4'

Они съели все пирожные, но снова просят.

In [12]: next(factory)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-12-0f5c45da9774> in <module>
----> 1 next(factory)

StopIteration:

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

In [13]: factory = make_cake(3)

In [14]: for cake in factory:
    ...:     print(cake)
    ...:
Cake 0
Cake 1
Cake 2

Вы также можете использовать цикл с генератором, как показано выше.

Еще один пример. Допустим, вам нужен случайный пароль всякий раз, когда вы его запрашиваете.

In [22]: import random

In [23]: import string

In [24]: def random_password_generator():
    ...:     while True:
    ...:         yield ''.join([random.choice(string.ascii_letters) for _ in range(8)])
    ...:

In [25]: rpg = random_password_generator()

In [26]: for i in range(3):
    ...:     print(next(rpg))
    ...:
FXpUBhhH
DdUDHoHn
dvtebEqG

In [27]: next(rpg)
Out[27]: 'mJbYRMNo'

Здесь rpg - генератор, который может генерировать бесконечное количество случайных паролей. Таким образом, мы можем также сказать, что генераторы полезны, когда мы не знаем длину последовательности в отличие от списка, который имеет конечное число элементов.

0
thavan