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

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

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

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

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

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

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


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

61 ответ

Решение

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

Ярлык в Гроккинг 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() Метод может выглядеть очень сложным и быть склонным к ошибкам. Здесь генераторы обеспечивают чистое и простое решение.

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

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

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

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

for i in some_function():
    print i

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

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

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

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

def some_func():
    return it()

for i in some_func():
    print i

Для более глубокого понимания того, что происходит за кулисами, for Цикл можно переписать так:

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

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

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

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

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

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

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

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

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

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

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

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

пример

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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


За кулисами

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

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

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


мелочи

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

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

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

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

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

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

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


Приложение:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

TL;DR

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

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

сделай это:

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

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

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


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

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

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

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

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

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

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

Урожай универсален. Это может делать бесконечные циклы:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

for item in sequence:

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

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

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

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

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

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

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

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

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

  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

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

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


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

def f():
  while True:
    yield 4

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

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

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

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

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

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

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

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

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

Хотя многие ответы показывают, почему вы используете 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()

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

yield from <expr>

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. Там приходит yield

замещать return с yield:

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

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

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

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

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

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

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

Это суть о yield,

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

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

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

  • return а также yield являются близнецами
  • list а также generator являются близнецами

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

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

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

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

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

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

Welcome to Racket v6.5.0.3.

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

Вот несколько примеров 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)

Использование замыканий объектов вместо генераторов (потому что http://c2.com/cgi/wiki?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)

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

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

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

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

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

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

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

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

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

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

Код машины:

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


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

    # function_to_create_the_next_batch_of_lightbulbs(barcodes)

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

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

Выход:

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

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

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


for i in f123():
    print i

Выход:

1 2 1 2 1 2 1 2

Как и предполагает каждый ответ, 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"

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

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

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

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

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

  1. ОО подход, мы оборачиваем метаданные as a class, Это так называемый iterator кто реализует протокол итератора (т.е. __next__(), а также __iter__() методы). Это также часто встречающийся шаблон проектирования итераторов.
  2. Функциональный подход, мы оборачиваем метаданные as a function, Это так называемый generator function, Но под капотом вернулся generator object еще 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

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

Урожай является объектом

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

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

Важнее, yield это барьер.

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

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

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

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

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

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

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

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