Что делает ключевое слово" 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.

30 ответов


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

Iterables

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

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

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

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

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

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

генераторы

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

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

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

доходность

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

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

генератор считается пустым один раз функция запускается, но не попадает

модуль itertools содержит специальные функции для управления iterables. Всегда хотите дублировать генератор? Цепочка из двух генераторов? Группировать значения во вложенном списке с одной строкой? 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)]

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

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

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


ярлык для нашарили yield

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

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

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

не путайте свои итераторы, итераторы и генераторы

сначала итератор протокол - когда вы пишите

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

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

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

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

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

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

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

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

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

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

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

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

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

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

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

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

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


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

итератор - это просто причудливый звучащий термин для объекта, который имеет метод 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

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

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


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

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

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

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


за кулисами

вот как " 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-speak, an типа Iterable - это любой объект, который "понимает концепцию for-loop", как список [1,2,3] и итератор является конкретным экземпляром запрошенного for-loop как [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 предложение по стандартам было отложено.


что значит yield ключевое слово do в Python?

Ответ Контур / Резюме

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

генераторы:

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

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

yield обеспечивает простой способ реализация протокола итератора, определяется следующими двумя методами: __iter__ и next (Python 2) или __next__ (Python 3). Оба этих метода сделать объект iterator, который можно ввести проверку с Iterator Абстрактные Базы Класс .

>>> 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 также позволяет делегацию subgenerators, которые будут объяснены в следующем разделе О делегировании сотрудничества с Sub-сопрограммы.

Coroutines:

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 is то же самое, что позвонить 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 ключевое слово do in В Python?

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


приложение:

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

  • он запутался в том, что делает типа Iterable, просто используя список в качестве примера. Смотрите мои ссылки выше, но в резюме: интерфейс Iterable имеет __iter__ способ возврата итератор. Ан итератор предоставляет .next (Python 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 core являются обсуждаем умоляюще пособия. Вот сообщение из списка рассылки:

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

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

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

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

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

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

  • SyntaxWarning или DeprecationWarning в 3.7
  • Py3k предупреждение в 2.7.x
  • SyntaxError в 3.8

Твое Здоровье, Ник.

-- Nick Coghlan / ncoghlan at gmail.com / Брисбен, Австралия

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

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

на return заявление в генераторе

на Python 2:

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

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

на Python 3:

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

сноски

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

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

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


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

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

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


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

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

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


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

Yield дает Вам генератор.

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

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


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

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

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


TL; DR

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

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

этого:

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

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

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


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

другой поведение:

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

доходность лень, это откладывает вычисления. Функция с выходом в ней фактически не выполняется на все!--47--> когда вы называете его. Объекта итератора возвращает использует магия для поддержания внутреннего контекста функции. Каждый раз, когда вы звоните next() на итераторе (это происходит в цикле for) выполнение дюймов вперед к следующему выходу. (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, потому что оба значения применение:

доходность - произведите или обеспечьте (как в земледелии)

...укажите следующие данные в серии.

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

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


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

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

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

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

в continuation passing style (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)

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

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


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

def f():
  while True:
    yield 4

это явно разумная итерация, поведение которой хорошо определено-каждый раз, когда генератор повторяет ее, он возвращает 4 (и делает это навсегда). Но вероятно, это не прототипический тип iterable, который приходит на ум при мысли об итераторах (т. е. 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 ключевое слово.


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

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

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

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

for item in sequence:

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

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

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


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

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

есть еще yield и значение (начиная с версии Python 3.3):

yield from <expr>

С Пеп 380 -- синтаксис для предоставления Subgenerator:

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

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

этой представит (начиная с Python 3.5):
async def new_coroutine(data):
   ...
   await blocking_action()

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


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

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

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


вот некоторые примеры 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)

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

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

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

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

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

вот демонстрация, которая использует структуру R6RS, но семантика абсолютно идентична 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
->

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

я предполагаю, что вы усвоили 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

см., вы получаете только одно число, а не a их список. 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 as return one of them и yield as 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 Близнецы

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

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 удерживает положение потока программы и следующий цикл начинается с позиции "выход". Кажется, что он ждет на этой позиции, а перед этим возвращает значение снаружи, и в следующий раз продолжает работать.

это, кажется, интересная и хорошая способность: D


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

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

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

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

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

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

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

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

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


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


Yield является объектом

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

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

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

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

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

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


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


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


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

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

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

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

существует 2 подхода для обертывания таких метаданных.

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

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


многие люди используют 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() дает генератор.

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


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


вот простой 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 петля, a list инициализатор, a 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 и посмотреть, что печатается get:

>>> 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 loop).


еще один TL; DR

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

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

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

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