Что делает ключевое слово «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 для метрических пространств. Это ссылка на полный источник: Module mspace .

вопрос задан 23.10.2008
Alex. S.
49175 репутация

38 ответов


  • 12468 рейтинг

    Чтобы понять, что делает 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... », - это истребитель; lists , strings , файлы ...

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

    Генераторы

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

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

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

    Уступать

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

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

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

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

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

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

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

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


    Ваш код объяснен

    Генератор:

     # Here you create the method of the node object that will return the generator
    def _get_child_candidates(self, distance, min_dist, max_dist):
    
        # Here is the code that will be called each time you use the generator object:
    
        # If there is still a child of the node object on its left
        # AND if distance is ok, return the next child
        if self._leftchild and distance - max_dist < self._median:
            yield self._leftchild
    
        # If there is still a child of the node object on its right
        # AND if distance is ok, return the next child
        if self._rightchild and distance + max_dist >= self._median:
            yield self._rightchild
    
        # If the function arrives here, the generator will be considered empty
        # there is no more than two values: the left and the right children
     

    Абонент:

     # Create an empty list and a list with the current object reference
    result, candidates = list(), [self]
    
    # Loop on candidates (they contain only one element at the beginning)
    while candidates:
    
        # Get the last candidate and remove it from the list
        node = candidates.pop()
    
        # Get the distance between obj and the candidate
        distance = node._get_dist(obj)
    
        # If distance is ok, then you can fill the result
        if distance <= max_dist and distance >= min_dist:
            result.extend(node._values)
    
        # Add the children of the candidate in the candidates list
        # so the loop will keep running until it will have looked
        # at all the children of the children of the children, etc. of the candidate
        candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
    
    return result
     

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

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

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

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

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

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

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

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

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

    Управление истощением генератора

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

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

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

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

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

    Тогда только import itertools .

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

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

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

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

    Об этом в этой статье больше говорится о работе for петель .

    ответ дан e-satis, с репутацией 342132, 23.10.2008
  • 1673 рейтинг

    Ярлык для Grokking yield

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

     def f123():
        yield 1
        yield 2
        yield 3
    
    for item in f123():
        print item
     

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

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

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

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

    ответ дан user28409, с репутацией 23078, 25.10.2008
  • 410 рейтинг

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

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

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

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

    ответ дан Jason Baker, с репутацией 101961, 23.10.2008
  • 354 рейтинг

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

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

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

     generator = myYieldingFunction(...)
    x = list(generator)
    
       generator
           v
    [x[0], ..., ???]
    
             generator
                 v
    [x[0], x[1], ..., ???]
    
                   generator
                       v
    [x[0], x[1], x[2], ..., ???]
    
                           StopIteration exception
    [x[0], x[1], x[2]]     done
    
    list==[x[0], x[1], x[2]]
     

    пример

    Давайте определим функцию makeRange , как и Python's range . Вызов makeRange(n) RETURNS A GENERATOR:

     def makeRange(n):
        # return 0,1,2,...,n-1
        i = 0
        while i < n:
            yield i
            i += 1
    
    >>> makeRange(5)
    <generator object makeRange at 0x19e4aa0>
     

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

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

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

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

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

    Однако есть одна большая разница; см. последний раздел.


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

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

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

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

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


    За кулисами

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

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

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


    мелочи

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

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

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

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

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

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

    ответ дан ninjagecko, с репутацией 56967, 19.06.2011
  • 263 рейтинг

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

    Ответить

    • Функция с yield при вызове возвращает генератор .
    • Генераторы - это итераторы, потому что они реализуют протокол итератора , поэтому вы можете перебирать их.
    • Генератор также может быть отправлен информацией , что делает его концептуально сопрограммой .
    • В 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's an iterable
    True
    >>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
    True                           # implements the iterator protocol.
     

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

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

    И, если необходимо, мы можем ввести тип:

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

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

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

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

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

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

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

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

     def func(an_iterable):
        yield from an_iterable
     

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

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

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

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

     def bank_account(deposited, interest_rate):
        while True:
            calculated_interest = interest_rate * deposited 
            received = yield calculated_interest
            if received:
                deposited += received
    
    
    >>> my_account = bank_account(1000, .05)
     

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

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

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

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

    Совместная делегация в субкорутине с yield from

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

     def money_manager(expected_rate):
        under_management = yield     # must receive deposited value
        while True:
            try:
                additional_investment = yield expected_rate * under_management 
                if additional_investment:
                    under_management += additional_investment
            except GeneratorExit:
                '''TODO: write function to send unclaimed funds to state'''
            finally:
                '''TODO: write function to mail tax info to client'''
    
    
    def investment_account(deposited, manager):
        '''very simple model of an investment account that delegates to a manager'''
        next(manager) # must queue up manager
        manager.send(deposited)
        while True:
            try:
                yield from manager
            except GeneratorExit:
                return manager.close()
     

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

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

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

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

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

     >>> my_account.close()
     

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

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

    Заключение

    По-моему, я затронул все аспекты следующего вопроса:

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

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


    Приложение:

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

    • Он смущен тем, что делает итерабельным , просто используя список в качестве примера. См. Мои ссылки выше, но в итоге: в iterable есть метод __iter__ возвращающий итератор . Итератор предоставляет .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 обсуждают вопрос о снижении его надбавки . Вот сообщение из списка рассылки:

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

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

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

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

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

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

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

    Привет, Ник.

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

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

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

    Вывод return в генераторе

    В Python 2 :

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

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

    В Python 3 :

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

    Сноски

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

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

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

    ответ дан Aaron Hall, с репутацией 153756, 25.06.2015
  • 235 рейтинг

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

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

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

    ответ дан Douglas Mayle, с репутацией 12811, 23.10.2008
  • 186 рейтинг

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

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

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

    ответ дан Claudiu, с репутацией 122117, 24.10.2008
  • 156 рейтинг

    Для тех, кто предпочитает минимальный рабочий пример, медитируйте на этом интерактивном сеансе 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
     
    ответ дан Daniel, с репутацией 1788, 18.01.2013
  • 135 рейтинг

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

     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 миллиардов. :)

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

    ответ дан RBansal, с репутацией 1359, 16.01.2013
  • 126 рейтинг

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

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

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

    ответ дан Jon Skeet, с репутацией 1057123, 23.10.2008
  • 123 рейтинг

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

    Оператор 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 (и делает это навсегда). Но, вероятно, это не прототипный тип итеративного, который приходит на ум при мыслите об итераторах (т. Е. 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 .

    ответ дан aestrivex, с репутацией 3142, 4.04.2013
  • 109 рейтинг

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

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

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

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

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

     for item in sequence:
     

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

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

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

    ответ дан tzot, с репутацией 61461, 24.10.2008
  • 97 рейтинг

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

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

    Например:

     >>> def coroutine():
    ...     i = -1
    ...     while True:
    ...         i += 1
    ...         val = (yield i)
    ...         print("Received %s" % val)
    ...
    >>> sequence = coroutine()
    >>> sequence.next()
    0
    >>> sequence.next()
    Received None
    1
    >>> sequence.send('hello')
    Received hello
    2
    >>> sequence.close()
     
    ответ дан Mike McKerns, с репутацией 16345, 4.02.2014
  • 86 рейтинг

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

     yield from <expr>
     

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

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

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

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

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

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

    ответ дан Sławomir Lenart, с репутацией 1575, 24.07.2014
  • 77 рейтинг

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

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

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

    ответ дан johnzachary, с репутацией 1860, 28.01.2013
  • 77 рейтинг

    Вот некоторые примеры 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)
     
    ответ дан Dustin Getz, с репутацией 12928, 3.10.2012
  • 70 рейтинг

    С точки зрения программирования итераторы реализованы как 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
    ->
     
    ответ дан alinsoar, с репутацией 7888, 21.08.2013
  • 63 рейтинг

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

     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

    ответ дан Engin OZTURK, с репутацией 1385, 20.12.2013
  • 54 рейтинг

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

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

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

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

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

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

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

     def normalFunction():
        return
        if False:
            pass
    
    def yielderFunction():
        return
        if False:
            yield 12
     

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

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

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

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

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

    ответ дан Evgeni Sergeev, с репутацией 11154, 14.06.2013
  • 42 рейтинг

    Как и каждый ответ, 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"
     
    ответ дан Mangu Singh Rajpurohit, с репутацией 5309, 29.07.2015
  • 38 рейтинг

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

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

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

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

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

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

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

    ответ дан Kaleem Ullah, с репутацией 3040, 1.09.2015
  • 34 рейтинг

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

    ответ дан An Epic Person, с репутацией 600, 20.05.2015
  • 33 рейтинг

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

    ответ дан Bahtiyar Özdere, с репутацией 908, 18.11.2015
  • 29 рейтинг

    Вот простой подход на основе 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's go through it again.")
     

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

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

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

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

    Неопознанные результаты - это то, что напечатано. Конечным результатом является то, что возвращается с yield . Вызовите next снова сейчас:

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

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

    ответ дан Jim Fasarakis Hilliard, с репутацией 70054, 20.02.2016