Вопрос

Какова польза от 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.

Это было полезно?

Решение

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

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

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

    Пример

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

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

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

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

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

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

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

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

    <Ч>

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

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

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

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

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

    <Ч>

    За кулисами

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

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

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

    <Ч>

    Minutiae

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

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

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

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

    Что это yield ключевое слово do в Python?

    Схема ответа/сводка

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

    Генераторы:

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

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

    yield обеспечивает простой способ реализация протокола итератора, определяемый следующими двумя методами:__iter__ и next (Питон 2) или __next__ (Питон 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.Это позволяет нам делегировать Coroutines в подкору:

    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 в ПЭП 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.
    • Итертулс?Это не имело отношения к тому, что 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
    

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

    Разработчики ядра CPython обсуждение отмены его пособия.Вот соответствующее сообщение из списка рассылки:

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

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

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

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

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

    Что касается достижения этой цели, нам, вероятно, понадобится:

    • SyntaxWarning или DeprecationWarning в версии 3.7
    • Предупреждение Py3k в версии 2.7.x
    • Синтаксическая ошибка в 3.8

    Приветствую, Ник.

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

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

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

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

    В Питон 2:

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

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

    В Питон 3:

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

    Сноски

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

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

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

    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
    

    ТЛ;ДР

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

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

    сделай это:

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

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

    Это был мой первый момент «ага» с выходом.


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Опять же, это довольно надуманный пример, вы, вероятно, использовали бы itertools, если бы вы действительно хотели сосчитать до 50 миллиардов. :)

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

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

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

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

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

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

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

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

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

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

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


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

    def f():
      while True:
        yield 4
    

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

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

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

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

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

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

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

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

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

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

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

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

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

    for item in sequence:
    

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

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

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

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

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

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

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

    from itertools import islice
    
    def fib_gen():
        a, b = 1, 1
        while True:
            yield a
            a, b = b, a + b
    
    assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))
    

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

    def ftake(fnext, last):
        return [fnext() for _ in xrange(last)]
    
    def fib_gen2():
        #funky scope due to python2.x workaround
        #for python 3.x use nonlocal
        def _():
            _.a, _.b = _.b, _.a + _.b
            return _.a
        _.a, _.b = 0, 1
        return _
    
    assert [1,1,2,3,5] == ftake(fib_gen2(), 5)
    

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

    class fib_gen3:
        def __init__(self):
            self.a, self.b = 1, 1
    
        def __call__(self):
            r = self.a
            self.a, self.b = self.b, self.a + self.b
            return r
    
    assert [1,1,2,3,5] == ftake(fib_gen3(), 5)
    

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

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

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

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

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

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

    "следующий" — это сообщение, отправленное на замыкание, созданное "итер" вызов.

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

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

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

    def isPrimeNumber(n):
        print "isPrimeNumber({}) call".format(n)
        if n==1:
            return False
        for x in range(2,n):
            if n % x == 0:
                return False
        return True
    
    def primes (n=1):
        while(True):
            print "loop step ---------------- {}".format(n)
            if isPrimeNumber(n): yield n
            n += 1
    
    for n in primes():
        if n> 10:break
        print "wiriting result {}".format(n)
    

    Выход:

    loop step ---------------- 1
    isPrimeNumber(1) call
    loop step ---------------- 2
    isPrimeNumber(2) call
    loop step ---------------- 3
    isPrimeNumber(3) call
    wiriting result 3
    loop step ---------------- 4
    isPrimeNumber(4) call
    loop step ---------------- 5
    isPrimeNumber(5) call
    wiriting result 5
    loop step ---------------- 6
    isPrimeNumber(6) call
    loop step ---------------- 7
    isPrimeNumber(7) call
    wiriting result 7
    loop step ---------------- 8
    isPrimeNumber(8) call
    loop step ---------------- 9
    isPrimeNumber(9) call
    loop step ---------------- 10
    isPrimeNumber(10) call
    loop step ---------------- 11
    isPrimeNumber(11) call
    

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

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

    Вот мысленное представление о том, что делает yield .

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

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

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

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

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

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

    for line in getNextLines():
        doSomeThing(line)
    

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

    Контроль выполнения будет перенесен из getNextLines () в цикл for при выполнении yield. Таким образом, каждый раз, когда вызывается 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"
    

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

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

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

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

      

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

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

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

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

    Многие люди используют 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 превращает функцию в генератор. Генератор ведет себя так же, как функция, пока что-то не «поддается». Генератор останавливается до следующего вызова и продолжает работу с той же точки, с которой он был запущен. Вы можете получить последовательность всех «полученных» значений в одном, вызвав list (generator ()) .

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

    Вот простой подход, основанный на yield , для вычисления ряда Фибоначчи:

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

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

    >>> fib()
    <generator object fib at 0x7fa38394e3b8>
    

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

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

    Используя встроенную функцию next () , вы напрямую вызываете .next / __ next __ , заставляя генератор вывести значение:

    >>> g = fib()
    >>> next(g)
    1
    >>> next(g)
    1
    >>> next(g)
    2
    >>> next(g)
    3
    >>> next(g)
    5
    

    Косвенно, если вы передаете fib в цикл for , инициализатор list , инициализатор tuple , или что-либо еще, что ожидает объект, который генерирует / производит значения, вы будете "потреблять" генератор до тех пор, пока он не сможет произвести больше значений (и он вернется):

    results = []
    for i in fib(30):       # consumes fib
        results.append(i) 
    # can also be accomplished with
    results = list(fib(30)) # consumes fib
    

    Аналогично, с инициализатором tuple :

    >>> tuple(fib(5))       # consumes fib
    (1, 1, 2, 3, 5)
    

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

    Когда вы впервые вызываете fib , вызывая его:

    f = fib()
    

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

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

    def yielder(value):
        """ This is an infinite generator. Only use next on it """ 
        while 1:
            print("I'm going to generate the value for you")
            print("Then I'll pause for a while")
            yield value
            print("Let's go through it again.")
    

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

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

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

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

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

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

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

    Простой пример, чтобы понять, что это такое: 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
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top