Что делает ключевое слово yield? - PullRequest
9261 голосов
/ 24 октября 2008

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

Ответы [ 38 ]

30 голосов
/ 22 июня 2016

Еще один TL; DR

Итератор в списке : next() возвращает следующий элемент списка

Генератор итераторов : next() будет вычислять следующий элемент на лету (выполнить код)

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

Примечание : Генератор НЕ нормальная функция. Он запоминает предыдущее состояние как локальные переменные (стек). Смотрите другие ответы или статьи для подробного объяснения. Генератор может быть повторен только один раз . Вы могли бы обойтись без yield, но это было бы не так хорошо, поэтому его можно считать «очень хорошим» языковым сахаром.

24 голосов
/ 29 апреля 2017

доходность аналогична доходности. Разница:

yield делает функцию итеративной (в следующем примере primes(n = 1) функция становится итеративной).
По сути, это означает, что при следующем вызове функции она будет продолжена с того места, где она вышла (после строки yield expression).

def isprime(n):
    if n == 1:
        return False
    for x in range(2, n):
        if n % x == 0:
            return False
    else:
        return True

def primes(n = 1):
   while(True):
       if isprime(n): yield n
       n += 1 

for n in primes():
    if n > 100: break
    print(n)

В приведенном выше примере, если isprime(n) истинно, он вернет простое число. На следующей итерации она продолжится со следующей строки

n += 1  
11 голосов
/ 03 октября 2017

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

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

Ваш код пересекает бинарную древовидную структуру. Давайте возьмем это дерево для примера:

    5
   / \
  3   6
 / \   \
1   4   8

И еще одна более простая реализация обхода дерева двоичного поиска:

class Node(object):
..
def __iter__(self):
    if self.has_left_child():
        for child in self.left:
            yield child

    yield self.val

    if self.has_right_child():
        for child in self.right:
            yield child

Код выполнения находится на объекте Tree, который реализует __iter__ следующим образом:

def __iter__(self):

    class EmptyIter():
        def next(self):
            raise StopIteration

    if self.root:
        return self.root.__iter__()
    return EmptyIter()

Оператор while candidates можно заменить на for element in tree; Python перевести это на

it = iter(TreeObj)  # returns iter(self.root) which calls self.root.__iter__()
for element in it: 
    .. process element .. 

Поскольку функция Node.__iter__ является генератором, код внутри нее выполняется за итерацию. Таким образом, исполнение будет выглядеть так:

  1. Корневой элемент является первым; проверить, не осталось ли у него потомков, и for повторить их (назовем его it1, потому что это первый объект итератора)
  2. у него есть ребенок, поэтому for выполняется. for child in self.left создает новый итератор из self.left, который сам является объектом Node (it2)
  3. Та же логика, что и у 2, и создается новый iterator (it3)
  4. Теперь мы достигли левого конца дерева. it3 не имеет левых детей, поэтому оно продолжается и yield self.value
  5. При следующем вызове next(it3) он вызывает StopIteration и существует, поскольку не имеет правых потомков (доходит до конца функции, ничего не возвращая)
  6. it1 и it2 все еще активны - они не исчерпаны, и вызов next(it2) даст значения, а не повысит StopIteration
  7. Теперь мы вернулись к контексту it2 и вызываем next(it2), который продолжается там, где он остановился: сразу после оператора yield child. Поскольку у него больше нет левых потомков, он продолжается и дает self.val.

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

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

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

8 голосов
/ 23 марта 2019

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

image

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

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

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

Код машины:

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


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

    # function_to_create_the_next_batch_of_lightbulbs(barcodes)

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

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

Выход:

How many lightbulbs to generate? 5
[10000, 10001, 10002, 10003, 10004]
Produce more? [Y/n]: y
How many lightbulbs to generate? 6
[10005, 10006, 10007, 10008, 10009, 10010]
Produce more? [Y/n]: y
How many lightbulbs to generate? 7
[10011, 10012, 10013, 10014, 10015, 10016, 10017]
Produce more? [Y/n]: n
8 голосов
/ 09 сентября 2018

В Python generators (специальный тип iterators) используются для генерации серии значений, а ключевое слово yield аналогично ключевому слову return функций генератора.

Еще одно увлекательное ключевое слово yield - сохранение state функции генератора .

Итак, мы можем установить number на другое значение каждый раз, когда generator дает.

Вот пример:

def getPrimes(number):
    while True:
        if isPrime(number):
            number = yield number     # a miracle occurs here
        number += 1

def printSuccessivePrimes(iterations, base=10):
primeGenerator = getPrimes(base)
primeGenerator.send(None)
for power in range(iterations):
    print(primeGenerator.send(base ** power))
6 голосов
/ 17 января 2018

Выход

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

Короче говоря , вы можете видеть, что цикл не останавливается и продолжает функционировать даже после отправки объекта или переменной (в отличие от return, где цикл останавливается после выполнения).

2 голосов
/ 22 февраля 2019

yield дает что-то. Это как будто кто-то просит тебя сделать 5 пирожных. Если вы закончили хотя бы с одним пирожным, вы можете дать им есть, пока вы готовите другие пирожные.

In [4]: def make_cake(numbers):
   ...:     for i in range(numbers):
   ...:         yield 'Cake {}'.format(i)
   ...:

In [5]: factory = make_cake(5)

Здесь factory называется генератором, который делает вам пирожные. Если вы позвоните make_function, вы получите генератор вместо запуска этой функции. Это потому, что когда ключевое слово yield присутствует в функции, оно становится генератором.

In [7]: next(factory)
Out[7]: 'Cake 0'

In [8]: next(factory)
Out[8]: 'Cake 1'

In [9]: next(factory)
Out[9]: 'Cake 2'

In [10]: next(factory)
Out[10]: 'Cake 3'

In [11]: next(factory)
Out[11]: 'Cake 4'

Они съели все пирожные, но снова просят один.

In [12]: next(factory)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-12-0f5c45da9774> in <module>
----> 1 next(factory)

StopIteration:

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

In [13]: factory = make_cake(3)

In [14]: for cake in factory:
    ...:     print(cake)
    ...:
Cake 0
Cake 1
Cake 2

Вы также можете использовать цикл for с генератором, как показано выше.

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

In [22]: import random

In [23]: import string

In [24]: def random_password_generator():
    ...:     while True:
    ...:         yield ''.join([random.choice(string.ascii_letters) for _ in range(8)])
    ...:

In [25]: rpg = random_password_generator()

In [26]: for i in range(3):
    ...:     print(next(rpg))
    ...:
FXpUBhhH
DdUDHoHn
dvtebEqG

In [27]: next(rpg)
Out[27]: 'mJbYRMNo'

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

1 голос
/ 17 августа 2018

Простая функция генератора

def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n

Оператор yield приостанавливает функцию, сохраняя все ее состояния, а затем продолжает последовательные вызовы.

https://www.programiz.com/python-programming/generator

...