Что делает ключевое слово "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. Код исходит от Jochen Schulz (jrschulz), который создал отличную библиотеку Python для метрических пространств. Это ссылка на полный источник: Модуль mspace.

8376
задан Alex. S. 24 окт. '08 в 1:21
источник поделиться

41 ответ

  • 1
  • 2

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

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

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

>>> 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... " on, является итерируемым; lists, strings, файлы...

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

Генераторы

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

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

Это точно так же, за исключением того, что вы использовали () вместо []. НО, вы не можете выполнить for я 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 create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything 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 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 петлю работы.

12249
ответ дан e-satis 24 окт. '08 в 1:48
источник поделиться

Ярлык для Грохот yield

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

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

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

Не путайте свои Итераторы, Итераторы и Генераторы

Во-первых, протокол итератора - при написании

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

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

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

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

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

  • Использует итератор для перебора элементов:

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

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

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

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

  • Встроенные списки, словари, кортежи, наборы, файлы.
  • Определенные пользователем классы, которые реализуют __iter__().
  • Генераторы.

Обратите внимание, что цикл 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, которая была ранее возвращена, выполняет следующую строку кода, в этом случае a yield и возвращает это как следующий элемент. Это происходит до тех пор, пока функция не выйдет, и в этот момент генератор поднимается StopIteration, и цикл выходит.

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

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

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

1645
ответ дан user28409 26 окт. '08 в 0:22
источник поделиться

Подумайте об этом так:

Итератор - просто причудливый зондирующий термин для объекта, который имеет следующий() метод. Таким образом, функция 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

Означает ли это больше смысла или просто путает вас больше? :)

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

402
ответ дан Jason Baker 24 окт. '08 в 1:28
источник поделиться

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

  • Если компилятор обнаруживает ключевое слово yield внутри функции, эта функция больше не возвращается с помощью инструкции return. Вместо он сразу возвращает ленивый "ожидающий список" объект, называемый генератором
  • Генератор повторяется. Что такое итерируемый? Это похоже на 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, как и Python range. Вызов 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() (и другие части протокола итерации) для реализации причудливых вещей, обычно за счет удобочитаемости, поэтому старайтесь избегать этого...


Minutiae

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

В Python-talk, итерабельным является любой объект, который "понимает концепцию for-loop", как список [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 отложено.

348
ответ дан ninjagecko 19 июня '11 в 9:33
источник поделиться

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

Ответ Схема/резюме

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

Генераторы:

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

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

yield обеспечивает простой способ реализовать протокол итератора, определяемый двумя следующими способами: __iter__ и next (Python 2) или __next__ (Python 3). Оба этих метода сделать объект итератором, который вы можете ввести с помощью Iterator Абстрактной базы Класс из модуля 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 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 заключается в том, что после исчерпания вы не можете повторно использовать или reset it:

>>> 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. Итератор предоставляет метод .next (Python 2 или .__next__ (Python 3), который неявно вызывается циклами for до тех пор, пока он не поднимает StopIteration, и как только он это сделает, он будет продолжайте это делать.
  • Затем он использует выражение генератора для описания того, что такое генератор. Поскольку генератор - это просто удобный способ создания iterator, он только смущает вопрос, и мы до сих пор еще не получили часть 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     нехорошо, ИМХО.

  
  

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

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

В плане получения там мы, скорее всего, захотим:

  • Синтаксис Предупреждение или устаревание Предупреждение в 3.7
  • Предупреждение Py3k в 2.7.x
  • SyntaxError в 3.8

Приветствия, Ник.

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

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

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

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

В Python 2:

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

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

В Python 3:

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

Сноски

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

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

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

255
ответ дан Aaron Hall 25 июня '15 в 9:11
источник поделиться

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

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

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

232
ответ дан Douglas Mayle 24 окт. '08 в 1:24
источник поделиться

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

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

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

182
ответ дан Claudiu 24 окт. '08 в 11:44
источник поделиться

Для тех, кто предпочитает минимальный рабочий пример, медитируйте на этом интерактивном сеансе 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
155
ответ дан Daniel 18 янв. '13 в 20:25
источник поделиться

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

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 миллионов? Мало того, что это огромный eater памяти, он также требует много времени, чтобы построить в то время, когда функция вызывается. Во втором случае бар просто дает вам генератор. Генератор является итерируемым, что означает, что вы можете использовать его в цикле for и т.д., Но каждое значение может быть доступно только один раз. Все значения также не сохраняются в памяти одновременно; объект-генератор "запоминает", где он был в цикле в последний раз, когда вы его назвали, - таким образом, если вы используете итерируемый (скажем) счет до 50 миллиардов, вам не нужно считать до 50 миллиардов всех сразу и хранить 50 миллиардов номеров для подсчета. Опять же, это довольно надуманный пример, вы, вероятно, будете использовать itertools, если вы действительно хотите сосчитать до 50 миллиардов.:)

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

133
ответ дан RBansal 16 янв. '13 в 9:42
источник поделиться

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

Там статья IBM, которая объясняет это достаточно хорошо (для Python), насколько я могу видеть.

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

125
ответ дан Jon Skeet 24 окт. '08 в 1:26
источник поделиться

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

Оператор 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)

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

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


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

def f():
  while True:
    yield 4

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

Повторить: Continuations может сохранять состояние стека программ, а генераторы могут сохранять состояние итерации. Это означает, что продолжения более мощные, чем генераторы, но также и то, что генераторов много, намного проще. Они легче реализовать разработчику языка, и им проще программировать (если у вас есть время для записи, попробуйте прочитать и понять эту страницу о продолжениях и вызовах /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.

121
ответ дан aestrivex 04 апр. '13 в 17:56
источник поделиться

TL; DR

Когда вы строите list с нуля...

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

... yield каждой части вместо

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

Это был мой первый "ага" момент с урожаем.


yield - это сахаристый способ сказать

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

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

>>> for square in squares_list(4):
...     print(square)
...
0
1
4
9
>>> for square in squares_the_yield_way(4):
...     print(square)
...
0
1
4
9

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

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

Доходность ленив, он откладывает вычисление. Функция с выходом в ней фактически не выполняется вообще, когда вы ее вызываете. Объект итератора, который он возвращает, использует магию для поддержки внутреннего контекста функции. Каждый раз, когда вы вызываете next() на итераторе (это происходит в for-loop), выполняете дюймы вперед до следующего урона. (return StopIteration и заканчивает серию).

Доходность универсальна. Он может выполнять бесконечные циклы:

>>> 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(squares_the_yield_way(4))
[0, 1, 4, 9]

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

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

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

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

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

119
ответ дан Bob Stein 25 марта '16 в 16:21
источник поделиться

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

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

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

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

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

for item in sequence:

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

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

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

109
ответ дан tzot 24 окт. '08 в 3:36
источник поделиться

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

Чтобы понять, что делает yield в следующем коде, вы можете использовать свой палец для отслеживания цикла через любой код с yield. Каждый раз, когда ваш палец попадает в yield, вы должны ждать ввода next или send. Когда вызывается next, вы прослеживаете код до тех пор, пока не нажмете yield... код справа от yield оценивается и возвращается вызывающему абоненту... тогда вы ждете. Когда next вызывается снова, вы выполняете другой цикл через код. Тем не менее, вы заметите, что в сопрограмме yield также можно использовать с send... который отправит значение от вызывающего в функцию yielding. Если задан a 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()
97
ответ дан Mike McKerns 04 февр. '14 в 5:27
источник поделиться

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

yield from <expr>

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

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

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

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

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

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

86
ответ дан Sławomir Lenart 25 июля '14 в 0:15
источник поделиться

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

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

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

78
ответ дан johnzachary 28 янв. '13 в 4:37
источник поделиться

Вот некоторые примеры 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)
77
ответ дан Dustin Getz 03 окт. '12 в 23:38
источник поделиться

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

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

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

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

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

Вот демонстрация, которая использует структуру 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
->
70
ответ дан alinsoar 21 авг. '13 в 22:01
источник поделиться

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

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

63
ответ дан Engin OZTURK 20 дек. '13 в 16:07
источник поделиться

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

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

Как аналогия, return и yield - близнецы. return означает "return and stop", тогда как "yield" означает "return, но continue",

  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 - близнецы
57
ответ дан JawSaw 14 нояб. '17 в 15:02
источник поделиться

Вот мысленный образ того, что делает 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 body.
 'send',
 'throw']

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

54
ответ дан Evgeni Sergeev 14 июня '13 в 19:36
источник поделиться

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

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

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

for line in getNextLines():
    doSomeThing(line)

Контроль выполнения

Управление выполнением будет передано из getNextLines() в цикл for когда будет выполнен выход. Таким образом, каждый раз, когда вызывается 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"
42
ответ дан Mangu Singh Rajpurohit 29 июля '15 в 9:11
источник поделиться

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

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

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

Что еще более важно, yield - это барьер.

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

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

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

37
ответ дан Kaleem Ullah 01 сент. '15 в 15:42
источник поделиться

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

34
ответ дан An Epic Person 20 мая '15 в 9:19
источник поделиться

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

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

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

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

Есть два подхода к обертке таких метаданных.

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

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

34
ответ дан smwikipedia 25 марта '16 в 8:40
источник поделиться

Таким образом, оператор 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', при следующем вызове он выполняет оператор increment, возвращается к "while", оценивает его, и если true, это остановится и снова вернет 'n', это будет продолжаться до тех пор, пока условие while не вернет false, и генератор перейдет в конец функции.

33
ответ дан redbandit 13 окт. '16 в 16:43
источник поделиться

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

33
ответ дан Bahtiyar Özdere 18 нояб. '15 в 22:37
источник поделиться

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

Вот пример, который yield, безусловно, лучше всего:

return (в функции)

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() дает генератор.

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

33
ответ дан Tom Fuller 10 сент. '16 в 14:37
источник поделиться

Здесь простой подход, основанный на 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, что вы хотите создать генератор, то есть объект, который генерирует значения по требованию.

Итак, как вы генерируете эти значения? Это можно сделать непосредственно, используя встроенную функцию 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 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 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).

29
ответ дан Jim Fasarakis Hilliard 20 февр. '16 в 20:41
источник поделиться

Ключевое слово yield

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

О генераторах Python Поскольку ключевое слово yield используется только с генераторами, имеет смысл сначала вспомнить концепцию генераторов.

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

>>> # First, we define a list
>>> the_list = [2**x for x in range(5)]
>>>
>>> # Type check: yes, it a list
>>> type(the_list)
<class 'list'>
>>>
>>> # Iterate over items and print them
>>> for element in the_list:
...     print(element)
...
1
2
4
8
16
>>>
>>> # How about the length?
>>> len(the_list)
5
>>>
>>> # Ok, now a generator.
>>> # As easy as list comprehensions, but with '()' instead of '[]':
>>> the_generator = (x+x for x in range(3))
>>>
>>> # Type check: yes, it a generator
>>> type(the_generator)
<class 'generator'>
>>>
>>> # Iterate over items and print them
>>> for element in the_generator:
...     print(element)
...
0
2
4
>>>
>>> # Everything looks the same, but the length...
>>> len(the_generator)
Traceback (most recent call last):
  File "", line 1, in
TypeError: object of type 'generator' has no len()

Итерация по списку, и генератор выглядит совершенно одинаково. Однако, хотя генератор итерируем, он не является коллекцией и, следовательно, не имеет длины. Коллекции (списки, кортежи, наборы и т.д.) Сохраняют все значения в памяти, и мы можем получить к ним доступ по мере необходимости. Генератор вычисляет значения "на лету" и забывает их, поэтому он не имеет никакого обзора о собственном наборе результатов.

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

Использование ключевого слова Python yield

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

Поскольку функция поиска не может быть создана с использованием методов list-assrehensions, мы собираемся определить генератор, используя функцию с выражением yield/keyword. Инструкция урока должна быть помещена в место, где генератор возвращает промежуточный результат вызывающему и спит до следующего вызова.

def search(keyword, filename):
    print('generator started')
    f = open(filename, 'r')
    # Looping through the file line by line
    for line in f:
        if keyword in line:
            # If keyword found, return it
            yield line
    f.close()

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

Счастливое питонирование! Для получения дополнительной информации перейдите к http://pythoncentral.io/python-generators-and-yield-keyword/

22
ответ дан drewteriyaki 23 марта '16 в 4:18
источник поделиться
  • 1
  • 2

Другие вопросы по меткам