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


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

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

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

И это вызывающий:

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

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


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


8154


источник


Ответы:


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

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

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

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

mylistявляется итерируемый , Когда вы используете понимание списка, вы создаете список и, следовательно, итерабельны:

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

Все, что вы можете использовать " for... in...«on is iterable; 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петли работают ,


11952



Ярлык для Grokking yield

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

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

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

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

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


1609



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

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

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

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


382



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

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

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

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

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

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

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

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

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

пример

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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


За кулисами

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

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

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


мелочи

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

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


336



Что это yieldключевое слово do в 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ключевое слово do в 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       нехорошо, ИМХО.

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

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

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

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

Привет, Ник.

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

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

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

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

В Python 2 :

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

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

В Python 3 :

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

Сноски

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

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

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


239



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

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

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


224



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

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

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


179



For those who prefer a minimal working example, meditate on this interactive Python session:

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

153



Yield gives you a generator.

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

As you can see, in the first case foo holds the entire list in memory at once. It's not a big deal for a list with 5 elements, but what if you want a list of 5 million? Not only is this a huge memory eater, it also costs a lot of time to build at the time that the function is called. In the second case, bar just gives you a generator. A generator is an iterable--which means you can use it in a for loop, etc, but each value can only be accessed once. All the values are also not stored in memory at the same time; the generator object "remembers" where it was in the looping the last time you called it--this way, if you're using an iterable to (say) count to 50 billion, you don't have to count to 50 billion all at once and store the 50 billion numbers to count through. Again, this is a pretty contrived example, you probably would use itertools if you really wanted to count to 50 billion. :)

This is the most simple use case of generators. As you said, it can be used to write efficient permutations, using yield to push things up through the call stack instead of using some sort of stack variable. Generators can also be used for specialized tree traversal, and all manner of other things.


130



It's returning a generator. I'm not particularly familiar with Python, but I believe it's the same kind of thing as C#'s iterator blocks if you're familiar with those.

There's an IBM article which explains it reasonably well (for Python) as far as I can see.

The key idea is that the compiler/interpreter/whatever does some trickery so that as far as the caller is concerned, they can keep calling next() and it will keep returning values - as if the generator method was paused. Now obviously you can't really "pause" a method, so the compiler builds a state machine for you to remember where you currently are and what the local variables etc look like. This is much easier than writing an iterator yourself.


122



There is one type of answer that I don't feel has been given yet, among the many great answers that describe how to use generators. Here is the programming language theory answer:

The yield statement in Python returns a generator. A generator in Python is a function that returns continuations (and specifically a type of coroutine, but continuations represent the more general mechanism to understand what is going on).

Continuations in programming languages theory are a much more fundamental kind of computation, but they are not often used, because they are extremely hard to reason about and also very difficult to implement. But the idea of what a continuation is, is straightforward: it is the state of a computation that has not yet finished. In this state, the current values of variables, the operations that have yet to be performed, and so on, are saved. Then at some point later in the program the continuation can be invoked, such that the program's variables are reset to that state and the operations that were saved are carried out.

Continuations, in this more general form, can be implemented in two ways. In the call/cc way, the program's stack is literally saved and then when the continuation is invoked, the stack is restored.

In continuation passing style (CPS), continuations are just normal functions (only in languages where functions are first class) which the programmer explicitly manages and passes around to subroutines. In this style, program state is represented by closures (and the variables that happen to be encoded in them) rather than variables that reside somewhere on the stack. Functions that manage control flow accept continuation as arguments (in some variations of CPS, functions may accept multiple continuations) and manipulate control flow by invoking them by simply calling them and returning afterwards. A very simple example of continuation passing style is as follows:

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)

In this (very simplistic) example, the programmer saves the operation of actually writing the file into a continuation (which can potentially be a very complex operation with many details to write out), and then passes that continuation (i.e, as a first-class closure) to another operator which does some more processing, and then calls it if necessary. (I use this design pattern a lot in actual GUI programming, either because it saves me lines of code or, more importantly, to manage control flow after GUI events trigger.)

The rest of this post will, without loss of generality, conceptualize continuations as CPS, because it is a hell of a lot easier to understand and read.


Now let's talk about generators in Python. Generators are a specific subtype of continuation. Whereas continuations are able in general to save the state of a computation (i.e., the program's call stack), generators are only able to save the state of iteration over an iterator. Although, this definition is slightly misleading for certain use cases of generators. For instance:

def f():
  while True:
    yield 4

This is clearly a reasonable iterable whose behavior is well defined -- each time the generator iterates over it, it returns 4 (and does so forever). But it isn't probably the prototypical type of iterable that comes to mind when thinking of iterators (i.e., for x in collection: do_something(x)). This example illustrates the power of generators: if anything is an iterator, a generator can save the state of its iteration.

To reiterate: Continuations can save the state of a program's stack and generators can save the state of iteration. This means that continuations are more a lot powerful than generators, but also that generators are a lot, lot easier. They are easier for the language designer to implement, and they are easier for the programmer to use (if you have some time to burn, try to read and understand this page about continuations and call/cc).

But you could easily implement (and conceptualize) generators as a simple, specific case of continuation passing style:

Whenever yield is called, it tells the function to return a continuation. When the function is called again, it starts from wherever it left off. So, in pseudo-pseudocode (i.e., not pseudocode, but not code) the generator's next method is basically as follows:

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

where the yield keyword is actually syntactic sugar for the real generator function, basically something like:

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

Remember that this is just pseudocode and the actual implementation of generators in Python is more complex. But as an exercise to understand what is going on, try to use continuation passing style to implement generator objects without use of the yield keyword.


118