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

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

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

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 .

9490
задан Alec Alameddine 2 May 2019 в 19:35
поделиться

7 ответов

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

Iterables

при создании списка можно считать его объекты один за другим. Чтение его объектов один за другим называют повторением:

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

mylist повторяемы . При использовании понимания списка Вы создаете список, и таким образом, повторяемое:

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

Все можно использовать" for... in..." на, повторяемое; lists, strings, файлы...

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

Генераторы

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

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

Это все равно кроме Вас, использовал () вместо [1 115]. НО, Вы не можете выполнять for i in mygenerator во второй раз, так как генераторы могут только использоваться однажды: они вычисляют 0, затем забывают об этом и вычисляют 1, и конец, вычисляющий 4, один за другим.

Урожай

yield является ключевым словом, которое используется как [1 118], кроме функции возвратит генератор.

>>> 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 the 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 the 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 candidate's 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 ожидает iterables, таким образом, это будет работать со строками, списками, кортежами и генераторами! Это называют утиным вводом и является одной из причин, почему 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 модуль содержит специальные функции для управления iterables. Когда-нибудь хотите копировать генератор? Цепочка два генератора? Группа оценивает во вложенном списке с остротой? 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)]

Понимание внутренних механизмов повторения

Повторение является процессом, подразумевающим iterables (реализация __iter__() метод) и итераторы (реализующий __next__() метод). Iterables являются любыми объектами, от которых можно получить итератор. Итераторы являются объектами, которые позволяют Вам выполнить итерации на iterables.

существует больше об этом в этой статье [приблизительно 1 135] как for работа циклов .

13752
ответ дан shaik moeed 2 May 2019 в 19:35
поделиться

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

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

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

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

не путают Ваш Iterables, Итераторы и Генераторы

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

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

, Python выполняет выполняющий двух шагов:

  1. Получает итератор для [1 110]:

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

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

  2. Использование итератор циклично выполнять по объектам:

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

истиной является Python, выполняет вышеупомянутые два шага каждый раз, когда это хочет к [1 148] цикл [более чем 1 148] содержание объекта - таким образом, это могло быть для цикла, но это мог также быть код как [1 119] (где 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

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

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

, Почему Генераторы Использования?

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

1856
ответ дан Arne 2 May 2019 в 19:35
поделиться

Думайте о нем этот путь:

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

Исходная версия:

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

, который имеет больше смысла или просто смущает Вас больше?:)

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

496
ответ дан Georgy 2 May 2019 в 19:35
поделиться

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

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

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

290
ответ дан Fang 2 May 2019 в 19:35
поделиться

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

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

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

217
ответ дан Claudiu 2 May 2019 в 19:35
поделиться

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

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

162
ответ дан Jon Skeet 2 May 2019 в 19:35
поделиться

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

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

  • я звоню Вам и говорю Вам, что хочу последовательность чисел, которая производится в особенном методе, и я сообщаю, каков алгоритм.
    Этот шаг соответствует def иннинг функция генератора, т.е. функция, содержащая yield.
  • Когда-то позже, я говорю Вам, "хорошо, подготовьтесь говорить мне последовательность чисел".
    Этот шаг соответствует вызыванию функции генератора, которая возвращает объект генератора. Примечание, что Вы еще не говорите мне чисел; Вы просто захватываете свою статью и карандаш.
  • я спрашиваю Вас, "скажите меня следующее число", и Вы говорите мне первое число; после этого Вы ожидаете меня для выяснения у Вас следующее число. Это - Ваше задание для запоминания, где Вы были, какие числа Вы уже сказали, и что является следующим числом. Я не забочусь о деталях.
    Этот шаг соответствует вызову .next() на объекте генератора.
  • †¦ повторяют предыдущий шаг, until†¦
  • в конечном счете, Вы могли бы закончиться. Вы не говорите мне число; Вы просто кричите, "держите свои лошади! Я сделан! Больше чисел!"
    Этот шаг соответствует объекту генератора, заканчивающему его задание и повышающему StopIteration исключение , функция генератора не должна повышать исключение. Это повышено автоматически, когда функциональные концы или выпуски a return.

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

самый известный пользователь протокола итератора эти for команда в Python. Так, каждый раз, когда Вы делаете a:

for item in sequence:

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

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

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

132
ответ дан Peter Mortensen 2 May 2019 в 19:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: