38 Вопрос: Что делает ключевое слово «yield»?

вопрос создан в Thu, May 2, 2019 12:00 AM

Какая польза от ключевого слова 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 .

    
9314
30 ответов                              30                         

Чтобы понять, что делает 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 циклов . р>     

13495
2019-05-02 19: 34: 54Z
  1. Все итераторы могут повторяться только один раз, а не только те, которые создаются функциями генератора. Если вы мне не верите, позвоните по номеру iter() для любого итерируемого объекта и попробуйте повторить результат более одного раза.
    2015-03-03 22: 54: 31Z
  2. @ Craicerjack Вы перепутали свои термины. Итерируемое - это что-то с методом __iter__. Итератор - это результат вызова iter() для итерируемого. Итераторы могут повторяться только один раз.
    2015-08-24 20: 52: 18Z
  3. yield не так волшебен, как предполагает этот ответ. Когда вы вызываете функцию, которая содержит оператор yield, вы получаете объект генератора, но код не запускается. Затем каждый раз, когда вы извлекаете объект из генератора, Python выполняет код в функции, пока не дойдет до оператора yield, затем приостанавливает и доставляет объект. Когда вы извлекаете другой объект, Python возобновляется сразу после yield и продолжается до тех пор, пока не достигнет другого yield (часто того же самого, но одной итерации позже). Это продолжается до тех пор, пока функция не завершится, и в этот момент генератор считается исчерпанным.
    2017-05-23 21: 41: 53Z
  4. "Эти итерируемые элементы удобны ... но вы храните все значения в памяти, и это не всегда то, что вам нужно", либо неверно, либо сбивает с толку. Итерируемый возвращает итератор после вызова iter () для итерируемого, и итератор не всегда должен хранить свои значения в памяти, в зависимости от реализации метода iter , он также может генерировать значения в последовательности по запросу.
    2018-02-15 19: 21: 11Z

Ярлык для понимания yield

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

  1. Вставьте строку result = [] в начале функции.
  2. Замените каждый yield expr на result.append(expr).
  3. Вставьте строку return result в нижней части функции.
  4. Да, не более 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() может выглядеть очень сложным и быть подверженным ошибкам. Здесь генераторы обеспечивают чистое и простое решение.

    
1810
2019-04-24 09: 37: 06Z
  1. "Когда вы видите функцию с операторами yield, примените этот простой прием, чтобы понять, что произойдет" Разве это не полностью игнорирует тот факт, что что вы можете send в генератор, который является огромной частью точки генераторов?
    2017-06-17 22: 41: 34Z
  2. "это может быть цикл for, но это также может быть код, подобный otherlist.extend(mylist)" - > Это неверно extend() изменяет список на месте и не возвращает итерацию. Попытка перебрать otherlist.extend(mylist) не удастся с TypeError, потому что extend() неявно возвращает None, и вы не можете перебрать None.
    2017-09-14 14: 48: 17Z
  3. @ pedro Вы неправильно поняли это предложение. Это означает, что Python выполняетдва упомянутых шага на mylist (не на otherlist) при выполнении otherlist.extend(mylist).
    2017-12-26 18: 53: 57Z

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

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

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

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

    
476
2019-05-07 13: 28: 35Z
  1. __getitem__ может быть определено вместо __iter__. Например: class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i), будет напечатано: 0, 10, 20, ..., 90
    2008-10-25 02: 03: 38Z
  2. Я пробовал этот пример в Python 3.6, и если я создаю iterator = some_function(), переменная iterator больше не имеет функцию с именем next(), а только функцию __next__(). Думаю, я упомяну это.
    2017-05-06 14: 37: 55Z

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

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

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

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

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

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

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

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

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

Пример

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

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

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

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

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

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

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

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

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

Однако есть одно существенное отличие; см. последний раздел.

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

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

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

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

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

За кулисами

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

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

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

Minutiae

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

В Python-говорящем итерируемый - это любой объект, который «понимает концепцию цикла for», такой как список [1,2,3], а итератор - это конкретный экземпляр запрошенного цикла вроде [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 предложение по стандартам было отложено.

    
402
2017-03-19 08: 07: 35Z
  

Что делает ключевое слово 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, которая будет указывать на данные tшляпа отправляется на генератор:

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

Приложение:

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

  • Это путает то, что делает итерируемым , просто используя список в качестве примера. См. Мои ссылки выше, но вкратце: итерируемый имеет метод __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       не хорошо, имхо.

    
         

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

  
     

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

     

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

     
  • SyntaxWarning или DeprecationWarning в 3.7
  •   
  • Py3k предупреждение в 2.7.x
  •   
  • Ошибка синтаксиса в 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 создает выражение доходности. https://docs.python.org/2/reference/simple_stmts .html # грамматику-токен-yield_stmt Это изменение было предложено разрешить пользователь для отправки данных в генератор так же, как можно получить это. Чтобы отправить данные, нужно иметь возможность назначить их чему-либо, и для этого заявление просто не будет работать.

321
2017-12-06 20: 20: 40Z

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

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

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

    
276
2019-01-24 09: 39: 59Z
  1. Это близко, но не правильно. Каждый раз, когда вы вызываете функцию с оператором yield, она возвращает совершенно новый объект-генератор. Только когда вы вызываете метод этого генератора .next (), выполнение возобновляется после последнего выхода.
    2008-10-24 18: 11: 04Z

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

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

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

    
209
2013-04-21 15: 42: 14Z

Для тех, кто предпочитает минимальный рабочий пример, медитируйте на этом интерактивном сеансе 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
    
184
2019-05-08 12: 20: 10Z

TL; DR сильный> р>

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

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

сделайте это:

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

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

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

yield - это сладкий способ сказать

  

создать серию вещей

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

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

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

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

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

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

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

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

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

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

  

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

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

  

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

... прекращайте выполнение процессора до тех пор, пока итератор не продвинется.

    
174
2019-01-04 15: 30: 21Z

Доходность дает вам генератор.

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

    
160
2019-03-13 06: 04: 08Z
  1. Просто примечание - в Python 3 range также возвращает генератор вместо списка, так что вы также увидите похожую идею, за исключением того, что __repr__/__str__ переопределяются, чтобы показать более хороший результат, в данном случае range(1, 10, 2).
    2019-03-21 18: 33: 13Z

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

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

        
    153
    2018-10-31 08: 42: 59Z

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

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

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

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

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

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

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

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


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

     
    def f():
      while True:
        yield 4
    

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

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

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

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

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

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

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

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

        
    140
    2018-05-20 10: 25: 32Z

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

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

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

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

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

     
    for item in sequence:
    

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

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

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

        
    126
    2018-05-20 10: 06: 05Z

    Хотя многие ответы показывают, почему вы используете 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()
    
        
    110
    2014-02-04 02: 27: 35Z
    1. Милый! батут (в смысле Лисп). Не часто это можно увидеть!
      2015-12-04 18: 31: 17Z

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

     
    yield from <expr>
    

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

      

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

         

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

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

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

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

        
    102
    2018-05-20 10: 34: 03Z

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

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

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

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

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

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

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

      
    1. приходит yield
    2.   

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

     
    In [10]: def num_list(n):
        ...:     for i in range(n):
        ...:         yield i
        ...:
    
    In [11]: num_list(3)
    Out[11]: <generator object num_list at 0x10327c990>
    
    In [12]: list(num_list(3))
    Out[12]: [0, 1, 2]
    

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

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

      
    1. Еще один шаг, который мы можем переписать с помощью yield.
    2.   
     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:

    Вы всегда будете получать [0, 1, 2] из объекта списка, но только сможетеполучить их один раз из «объекта yield». Таким образом, у него есть новое имя yield объекта, как показано в generator.

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

    •  Out[11]: <generator object num_list at 0x10327c990> и return - близнецы
    •  yield и list - близнецы
    88
    2018-05-28 09: 06: 22Z
    1. Это понятно, но одно существенное отличие состоит в том, что вы можете иметь несколько выходов в функции /методе. Аналогия полностью нарушается в этой точке. Выход запоминает свое место в функции, поэтому при следующем вызове next () ваша функция переходит к следующему generator. Это важно, я думаю, и должно быть выражено.
      2018-08-23 13: 27: 21Z

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

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

     yield

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

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

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

    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)
    
        
    86
    2017-10-24 10: 46: 05Z

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

    Также обратите внимание, что

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

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

        
    82
    2013-01-28 01: 37: 10Z

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

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

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

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

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

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

     yield
        
    80
    2018-05-20 10: 29: 34Z

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

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

    Вывод:

     
    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)
    

    Я не являюсь разработчиком Python, но мне кажется,

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

    Кажется, это интересная и приятная способность:D р>     

    72
    2018-05-20 10: 31: 01Z
    1. Не забывайте, что 2 простое число: -)
      2018-07-02 01: 20: 23Z
    2. Вы правы. Но что влияет на поток, чтобы увидеть поведение «доходности»? Я могу изменить алгоритм во имя математики. Поможет ли это получить другую оценку «доходности»?
      2018-07-02 01: 44: 08Z

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

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

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

    С помощью функции 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, мы можем подтвердить, что наша ментальная модель, представленная выше, заслуживает доверия.     
    59
    2017-03-01 13: 36: 58Z

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

     yield

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

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

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

    Контроль выполнения будет перенесен из getNextLines () в цикл

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

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

     for

    распечатает

     
    def simpleYield():
        yield "first time"
        yield "second time"
        yield "third time"
        yield "Now some useful value {}".format(12)
    
    for i in simpleYield():
        print i
    
        
    50
    2018-05-20 10: 42: 59Z

    Доход - это объект

    "first time"
    "second time"
    "third time"
    "Now some useful value 12"
    
    в функции вернет одно значение.

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

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

      

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

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

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

        
    44
    2018-05-20 10: 45: 50Z

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

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

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

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

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

    1. В ОО-подходе мы заключаем метаданные the logic how the data is computed. Это так называемый as a class, который реализует протокол итератора (то есть методы iterator и __next__()). Это также часто встречающийся шаблон дизайна итераторов .
    2. Функциональный подход, мы заключаем метаданные __iter__(). Это так называемый as a function. Но под капотом возвращенный generator function остается итератором generator object, поскольку он также реализует протокол итератора.

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

        
    44
    2018-11-23 01: 38: 59Z

    Таким образом, оператор IS-A преобразует вашу функцию в фабрику, которая создает специальный объект, называемый 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
    
    выполняет функцию, пока не достигнет оператора yield, в котором она останавливается и «возвращает» значение, или не достигаетконец функции. В этом случае при первом вызове next() выполняется до оператора yield и выдает «n», при следующем вызове он выполняет оператор приращения, возвращается к «времени», оценивает его и, если оно истинно, останавливается. и снова выдает 'n', так будет продолжаться до тех пор, пока условие while не вернет false и генератор не перейдет к концу функции.     
    43
    2018-05-20 11: 04: 36Z

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

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

        
    41
    2018-05-20 11: 02: 52Z

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

        
    36
    2015-05-20 06: 19: 32Z

    Ключевое слово list(generator()) просто собирает возвращаемые результаты. Подумайте о yield как yield

        
    35
    2016-04-23 03: 16: 19Z

    Вот простой return +=-ориентированный подход для вычисления ряда Фибоначчи:

     yield

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

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

    Это связано с тем, что

    >>> fib()
    <generator object fib at 0x7fa38394e3b8>
    
    сообщил Python, что вы хотите создать генератор , то есть объект, который генерирует значения по требованию.

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

    Используя встроенную функцию 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

    Python компилирует функцию, встречает ключевое слово

    f = fib()
    
    и просто возвращает объект генератора обратно к вам. Не очень полезно, кажется.

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

     print "text"

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

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

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

    >>> 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).     
    32
    2017-07-12 12: 44: 24Z

    Простой пример, чтобы понять, что это такое: while

     yield

    Вывод:

     
    def f123():
        for _ in range(4):
            yield 1
            yield 2
    
    
    for i in f123():
        print i
    
        
    32
    2019-06-06 12: 08: 26Z
    1 2 1 2 1 2 1 2
    
источник размещен Вот