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

avatar
Alex. S.
23 октября 2008 в 22:21
2602353
47
11357

Какое использование ключевого слова 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] [1].
Источник
Charlie Parker
30 июня 2021 в 19:04
7

yield не волшебный, как предполагает главный ответ. Отличный комментарий @ mattias-fripp: When you call a function that has a yield statement, you get a generator object, but no code runs. Then each time you extract an object from the generator, Python executes the function until it reaches a yield statement, then pauses and delivers the object. When you extract another object, Python resumes just after the yield and continues until it reaches another yield (often the same one, but one iteration later). This continues until the function runs off the end, at which point the generator is deemed exhausted.

Ответы (47)

avatar
e-satis
23 октября 2008 в 22:48
16151

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

Итерационные объекты

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

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

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

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

Все, что вы можете использовать для «for... in...», является итерируемым; lists, strings, файлы ...

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

Генераторы

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

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

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

Доходность

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

>>> def create_generator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = create_generator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object create_generator 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 ожидает итераций, поэтому он будет работать со строками, списками, кортежами и генераторами! Это называется утиной типизацией и является одной из причин, по которой 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 циклы.

Matthias Fripp
23 мая 2017 в 21:41
517

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

picmate 涅
15 февраля 2018 в 19:21
54

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

WoJ
7 мая 2020 в 10:12
4

Было бы неплохо добавить к этому отличный ответ, почему Он такой же, за исключением того, что вы использовали () вместо [] , в частности, что такое () (может быть путаница с кортежем).

aderchox
8 мая 2020 в 12:06
1

Возможно, я ошибаюсь, но генератор - это не итератор, а «вызываемый генератор» - это итератор.

alani
6 июня 2020 в 06:03
19

@MatthiasFripp «Это продолжается до тех пор, пока функция не завершится до конца» - или пока не встретится оператор return. (return разрешено в функции, содержащей yield, при условии, что она не указывает возвращаемое значение.)

Matthias Fripp
9 июня 2020 в 00:03
1

@alaniwi: хороший момент. Думаю, примерно так же и происходит, когда функция работает сама по себе.

Jacob Ward
3 декабря 2020 в 01:23
1

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

Zaffer
17 июля 2021 в 16:40
0

Итак, yield в любом месте функции делает функцию генератором, который, по сути, является итерацией выходов из функции?

avatar
Vinod Srivastav
7 января 2022 в 05:40
2

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

# example A
def getNumber():
    for r in range(1,10):
        return r

Приведенная выше функция вернет только 1, даже если она вызывается несколько раз. Теперь, если мы заменим return на yield как в пример B:

# example B
def getNumber():
    for r in range(1,10):
        yield r

Это вернется 1 Когда впервые называется 2 Когда звонит снова тогда 3 4 И он идет к увеличению до 10.

Хотя пример B концептуально верен, но чтобы вызвать его в python 3, мы должны сделать следующее:


g = getNumber() #instance
print(next(g)) #will print 1
print(next(g)) #will print 2
print(next(g)) #will print 3

# so to assign it to a variables
v = getNumber()
v1 = next(v) #v1 will have 1
v2 = next(v) #v2 will have 2
v3 = next(v) #v3 will have 3
Eissaweb
9 февраля 2022 в 16:04
0

Понятно и просто, спасибо.

avatar
yash bhangare
29 декабря 2021 в 16:02
0

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

Пример ниже демонстрирует работу его:

def counter():
    x=2
    while x < 5:
        yield x
        x += 1
        
print("Initial value of x: ", counter()) 

x=2
x=x+1

for y in counter():
    print(y)

Приведенный выше код генерирует следующий вывод:

Начальное значение x: <счетчик объекта-генератора в 0x7f0263020ac0>

2

3

4

avatar
Conjure.Li
29 декабря 2021 в 08:32
0

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

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

Все структуры данных, которые могут использоваться для ... in ..., являются повторяемыми; списки, строки, файлы ...

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

генератор: генератор, генератор, генератор вырабатывает электричество, но не накапливает энергию;)

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

Пока () используется вместо [], понимание списка становится пониманием генератора. Однако, поскольку генератор можно использовать только один раз, вы не можете выполнить для i в mygenerator второй раз: генератор вычисляет 0, затем отбрасывает его, затем вычисляет 1 и в последний раз вычисляет 4. Типичный черный слепой ломает кукурузу. .

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

>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() 
>>> print(mygenerator) 
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4 

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

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

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

Самая сложная часть сейчас:

При первом вызове объекта-генератора, созданного из вашей функции, он будет запускать код функции с самого начала, пока не достигнет yield, а затем вернет первое значение цикла. Затем каждый последующий вызов будет запускать следующую итерацию цикла, который вы написали в функции, и возвращать следующее значение. Это будет продолжаться до тех пор, пока генератор не будет считаться пустым, что даст результат, если во время выполнения функции не будет попаданий. Это может быть из-за того, что цикл закончился или вас больше не устраивает «if / else».

Личное понимание Я надеюсь помочь вам!

avatar
michalmonday
5 декабря 2021 в 11:14
1

Генераторы позволяют немедленно получать отдельные обработанные элементы (без необходимости ждать обработки всей коллекции). Это показано в примере ниже.

import time

def get_gen():
    for i in range(10):
        yield i
        time.sleep(1)

def get_list():
    ret = []
    for i in range(10):
        ret.append(i)
        time.sleep(1)
    return ret


start_time = time.time()
print('get_gen iteration (individual results come immediately)')
for i in get_gen():
    print(f'result arrived after: {time.time() - start_time:.0f} seconds')
print()

start_time = time.time()
print('get_list iteration (results come all at once)') 
for i in get_list():
    print(f'result arrived after: {time.time() - start_time:.0f} seconds')

get_gen iteration (individual results come immediately)
result arrived after: 0 seconds
result arrived after: 1 seconds
result arrived after: 2 seconds
result arrived after: 3 seconds
result arrived after: 4 seconds
result arrived after: 5 seconds
result arrived after: 6 seconds
result arrived after: 7 seconds
result arrived after: 8 seconds
result arrived after: 9 seconds

get_list iteration (results come all at once)
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
avatar
Ted Shaneyfelt
9 ноября 2021 в 06:28
4

Простой вариант использования:

>>> def foo():
    yield 100
    yield 20
    yield 3

    
>>> for i in foo(): print(i)

100
20
3
>>> 

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

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

В некоторых языках, таких как ES6 и выше, это реализовано немного иначе, поэтому следующая функция - член объекта-генератора, и вы можете передавать значения от вызывающей стороны каждый раз, когда она получает следующее значение. Итак, если результат является генератором, вы можете сделать что-то вроде y = result.next (555), а программа, выдающая значения, могла бы сказать что-то вроде z = yield 999. Значение y будет 999, которое затем будет получено из yield, и значение z будет 555, что дает доход от следующего. Python, похоже, этого не делает (пока? Может, когда-нибудь?)

avatar
ToTamire
3 октября 2021 в 17:18
4

Простой ответ

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

Контрольный показатель

Создайте список и верните его:

def my_range(n):
    my_list = []
    i = 0
    while i < n:
        my_list.append(i)
        i += 1
    return my_list

@profile
def function():
    my_sum = 0
    my_values = my_range(1000000)
    for my_value in my_values:
        my_sum += my_value

function()

Результаты с:

Total time: 1.07901 s
Timer unit: 1e-06 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     9                                           @profile
    10                                           def function():
    11         1          1.1      1.1      0.0      my_sum = 0
    12         1     494875.0 494875.0     45.9      my_values = my_range(1000000)
    13   1000001     262842.1      0.3     24.4      for my_value in my_values:
    14   1000000     321289.8      0.3     29.8          my_sum += my_value



Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
     9   40.168 MiB   40.168 MiB           1   @profile
    10                                         def function():
    11   40.168 MiB    0.000 MiB           1       my_sum = 0
    12   78.914 MiB   38.746 MiB           1       my_values = my_range(1000000)
    13   78.941 MiB    0.012 MiB     1000001       for my_value in my_values:
    14   78.941 MiB    0.016 MiB     1000000           my_sum += my_value

Генерация значений на лету:

def my_range(n):
    i = 0
    while i < n:
        yield i
        i += 1

@profile
def function():
    my_sum = 0
    
    for my_value in my_range(1000000):
        my_sum += my_value

function()

Результаты с:

Total time: 1.24841 s
Timer unit: 1e-06 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     7                                           @profile
     8                                           def function():
     9         1          1.1      1.1      0.0      my_sum = 0
    10
    11   1000001     895617.3      0.9     71.7      for my_value in my_range(1000000):
    12   1000000     352793.7      0.4     28.3          my_sum += my_value



Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
     7   40.168 MiB   40.168 MiB           1   @profile
     8                                         def function():
     9   40.168 MiB    0.000 MiB           1       my_sum = 0
    10
    11   40.203 MiB    0.016 MiB     1000001       for my_value in my_range(1000000):
    12   40.203 MiB    0.020 MiB     1000000           my_sum += my_value

Сводка

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

avatar
Saurabh Dhage
13 сентября 2021 в 15:40
3

Простыми словами

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

Давайте посмотрим на примере:

# A Simple Python program to demonstrate working
# of yield
  
# A generator function that yields 1 for the first time,
# 2 second time and 3 third time
def simpleGeneratorFun():
    yield 1
    yield 2
    yield 3
  

Код драйвера для проверки вышеуказанной функции генератора

for value in simpleGeneratorFun(): 
    print(value)

Output:

1
2
3

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

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

avatar
Mayank Maheshwari
14 июня 2021 в 19:26
1

Обычно он используется для создания итератора вне функции. Подумайте, что «yield» - это добавление () к вашей функции, а ваша функция - как массив. И если определенные критерии соответствуют, вы можете добавить это значение в свою функцию, чтобы сделать ее итератором.

arr=[]
if 2>0:
   arr.append(2)

def func():
   if 2>0:
      yield 2

вывод будет одинаковым для обоих.

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

avatar
Siva Sankar
14 июня 2021 в 12:27
0

Функция - возвращает.

Генератор - доходность (содержит одну или несколько доходностей и ноль или несколько доходностей).

names = ['Sam', 'Sarah', 'Thomas', 'James']


# Using function
def greet(name) :
    return f'Hi, my name is {name}.'
    
for each_name in names:
    print(greet(each_name))

# Output:   
>>>Hi, my name is Sam.
>>>Hi, my name is Sarah.
>>>Hi, my name is Thomas.
>>>Hi, my name is James.


# using generator
def greetings(names) :
    for each_name in names:
        yield f'Hi, my name is {each_name}.'
 
for greet_name in greetings(names):
    print (greet_name)

# Output:    
>>>Hi, my name is Sam.
>>>Hi, my name is Sarah.
>>>Hi, my name is Thomas.
>>>Hi, my name is James.

Генератор выглядит как функция, но ведет себя как итератор.

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

def function():
    yield 1 # return this first
    yield 2 # start continue from here (yield don't execute above code once executed)
    yield 3 # give this at last (yield don't execute above code once executed)

for processed_data in function(): 
    print(processed_data)
    
#Output:

>>>1
>>>2
>>>3

Примечание: Выход не должен входить в конструкцию try ... finally.

avatar
Aaron_ab
22 августа 2020 в 06:48
13

Может также отправлять данные обратно в генератор!

Действительно, как объясняют многие ответы здесь, использование yield создает generator.

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

Пример:

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

def translator():
    # load all the words in English language and the translation to 'other lang'
    my_words_dict = {'hello': 'hello in other language', 'dog': 'dog in other language'}

    while True:
        word = (yield)
        yield my_words_dict.get(word, 'Unknown word...')

Работает:

my_words_translator = translator()

next(my_words_translator)
print(my_words_translator.send('dog'))

next(my_words_translator)
print(my_words_translator.send('cat'))

напечатает:

dog in other language
Unknown word...

Подводя итог:

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

avatar
Swati Srivastava
17 января 2020 в 10:17
9

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

def fun():
   yield 1
   yield 2
   yield 3

И вызывающий абонент: -

def caller():
   print ('First value printing')
   print (fun())
   print ('Second value printing')
   print (fun())
   print ('Third value printing')
   print (fun())

Вышеупомянутый сегмент кода (функция вызывающего абонента) при вызове выводит: -

First value printing
1
Second value printing
2
Third value printing
3

Как видно из вышеизложенного, yield возвращает значение вызывающей стороне, но когда функция вызывается снова, она начинается не с первого оператора, а с оператора сразу после yield. В приведенном выше примере была напечатана «Печать первого значения» и была вызвана функция. 1 был возвращен и напечатан. Затем была напечатана «печать второго значения» и снова был вызван fun (). Вместо того, чтобы печатать 1 (первый оператор), он вернул 2, т.е. оператор сразу после yield 1. Тот же процесс повторяется и дальше.

Funny Geeks
30 апреля 2020 в 18:03
0

Если вы попытаетесь запустить этот код, print(fun()) не будет печатать числа. Вместо этого он печатает представление объекта генератора, возвращенного fun() (что-то вроде <generator object fun at 0x6fffffe795c8>)

Swati Srivastava
2 мая 2020 в 20:16
0

@FunnyGeeks Я запустил тот же код на Jupyter Notebook, и он отлично работает. Кроме того, здесь нужно было объяснить работу ключевого слова yield. Фрагмент предназначен только для демонстрации.

Funny Geeks
3 мая 2020 в 21:31
0

Я пробовал это в python2 и python3 в моей консоли cygwin. Это не сработало. github.com/ImAmARobot/PythonTest

avatar
Rafael
23 марта 2019 в 13:55
37

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

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

Генераторы 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
avatar
thavan
22 февраля 2019 в 12:11
26

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

avatar
Andy Fedoroff
9 сентября 2018 в 13:25
26

В 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))
avatar
AbstProcDo
14 ноября 2017 в 12:02
135

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

Я полагаю, вы усвоили инструкцию return.

По аналогии, return и yield - близнецы. return означает «возврат и остановка», тогда как «yield» означает «возврат, но продолжить»

  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 близнецы
Mike S
23 августа 2018 в 13:27
3

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

avatar
Chen A.
3 октября 2017 в 11:30
20

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

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

Ваш код проходит через двоичную древовидную структуру. Возьмем, к примеру, это дерево:

    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, который является самим объектом узла (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).

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

avatar
blueray
29 апреля 2017 в 17:22
37

yield аналогичен return. Разница:

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  
avatar
Gavriel Cohen
2 января 2017 в 12:09
70

Простой пример, чтобы понять, что это такое: 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
user9074332
5 февраля 2020 в 04:05
5

ты уверен в этом выходе? Разве это не будет напечатано только в одной строке, если вы выполнили этот оператор печати с помощью print(i, end=' ')? В противном случае я считаю, что поведение по умолчанию поместит каждое число в новую строку

Gavriel Cohen
5 февраля 2020 в 14:58
1

@ user9074332, Вы правы, но написано в одну строчку для облегчения понимания

avatar
redbandit
13 октября 2016 в 13:43
65

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

def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

просто выводит

one
two
three

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

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

def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

и используйте его так:

for i in myRangeNaive(10):
    print i

Но это неэффективно, потому что

  • Вы создаете массив, который используете только один раз (это расходует память)
  • Этот код фактически дважды перебирает этот массив! :(

К счастью, Гвидо и его команда были достаточно щедры, чтобы разработать генераторы, так что мы могли просто сделать это;

def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return

for i in myRangeSmart(10):
    print i

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

avatar
Tom Fuller
10 сентября 2016 в 11:37
60

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

Это результат кода:

Output

Как видите, обе функции делают одно и то же. Единственная разница в том, что return_dates() дает список, а yield_dates() дает генератор.

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

avatar
Christophe Roussy
22 июня 2016 в 09:40
45

Еще один TL; DR

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

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

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

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

avatar
Bob Stein
25 марта 2016 в 13:21
241

TL;DR

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

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.


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

Другое поведение:

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

Доходность ленивая , откладывает вычисления. Функция с yield в ней на самом деле вообще не выполняется, когда вы ее вызываете. Она возвращает объект итератора, который запоминает, где она остановилась. Каждый раз, когда вы вызываете next() на итераторе (это происходит в цикле for), выполнение смещается вперед до следующего yield. 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, потому что применимы оба значения:

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

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

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

... прекращать выполнение ЦП, пока итератор не продвинется вперед.

avatar
smwikipedia
25 марта 2016 в 05:40
66

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

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

avatar
Dimitris Fasarakis Hilliard
20 февраля 2016 в 17:41
41

Вот простой подход, основанный на 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 еще раз:

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

avatar
Bahtiyar Özdere
18 ноября 2015 в 19:37
49

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

avatar
Kaleem Ullah
1 сентября 2015 в 12:42
61

Доходность - это объект

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

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

Что еще более важно, yield - это барьер .

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

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

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

avatar
Mangu Singh Rajpurohit
29 июля 2015 в 06:11
69

Как следует из каждого ответа, 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"
avatar
Aaron Hall
25 июня 2015 в 06:11
458

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

Краткое содержание ответа

Генераторы:

yield разрешен только внутри определения функции, а включение yield в определение функции приводит к возврату генератора. <expression_list4290> <2333>

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

yield обеспечивает простой способ реализации протокола итератора, определяемый следующими двумя методами: __iter__ и next (Python 2) или __next__ (Python 3). Оба эти метода сделать объект итератором, который можно было бы проверить с помощью Iterator абстрактной базы Класс из модуля collections.

>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

Тип генератора является подтипом итератора:

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

И при необходимости мы можем проверить тип следующим образом:

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

Особенностью Iterator является то, что после исчерпания вы не можете повторно использовать или сбросить его:

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

Вам придется сделать еще один, если вы хотите снова использовать его функции (см. Сноску 2):

>>> list(func())
['I am', 'a generator!']

Данные можно получить программно, например:

def func(an_iterable):
    for item in an_iterable:
        yield item

Вышеупомянутый простой генератор также эквивалентен приведенному ниже - начиная с Python 3.3 (и не доступен в Python 2), вы можете использовать yield from:

def func(an_iterable):
    yield from an_iterable

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

Сопрограммы:

yield формирует выражение, которое позволяет отправлять данные в генератор (см. Сноску 3)

Вот пример, обратите внимание на переменную received, которая будет указывать на данные, отправляемые в генератор:

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

Сначала мы должны поставить генератор в очередь со встроенной функцией next. Так и будет вызовите соответствующий метод next или __next__, в зависимости от версии Python, который вы используете:

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

А теперь мы можем отправлять данные в генератор. (Отправка None - это то же, что и вызов next.):

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

Совместное делегирование подпрограммы с yield from

Теперь напомним, что yield from доступен в Python 3. Это позволяет нам делегировать сопрограммы подпрограмме:


def money_manager(expected_rate):
    # must receive deposited value from .send():
    under_management = yield                   # yield None to start.
    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'''
            raise
        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'''
    # must queue up manager:
    next(manager)      # <- same as manager.send(None)
    # This is where we send the initial deposit to the manager:
    manager.send(deposited)
    try:
        yield from manager
    except GeneratorExit:
        return manager.close()  # delegate?

И теперь мы можем делегировать функции субгенератору, и его можно использовать генератором, как указано выше:

my_manager = money_manager(.06)
my_account = investment_account(1000, my_manager)
first_year_return = next(my_account) # -> 60.0

Теперь смоделируйте добавление еще 1000 на счет плюс доходность счета (60,0):

next_year_return = my_account.send(first_year_return + 1000)
next_year_return # 123.6

Вы можете узнать больше о точной семантике yield from в PEP 380.

Другие методы: закрыть и выбросить

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

my_account.close()

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

import sys
try:
    raise ValueError
except:
    my_manager.throw(*sys.exc_info())

Повышает:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 6, in money_manager
  File "<stdin>", line 2, in <module>
ValueError

Заключение

Мне кажется, я рассмотрел все аспекты следующего вопроса:

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

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


Приложение:

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

  • Он не понимает, что делает итерабельным , просто используя список в качестве примера. См. Мои ссылки выше, но вкратце: итерация имеет метод __iter__, возвращающий итератор . Итератор предоставляет метод .next (Python 2 или .__next__ (Python 3), который неявно вызывается циклами for до тех пор, пока он не вызовет StopIteration, и как только это произойдет, он продолжит для этого.
  • Затем он использует выражение генератора, чтобы описать, что такое генератор. Поскольку генератор - это просто удобный способ создания итератора итератора , он только сбивает с толку, и мы еще не добрались до части yield.
  • В разделе Управление истощением генератора он вызывает метод .next, тогда как вместо этого он должен использовать встроенную функцию next. Это был бы подходящий уровень косвенного обращения, потому что его код не работает в Python 3.
  • Itertools? Это не имело отношения к тому, что делает yield.
  • Никакого обсуждения методов, которые yield предоставляет вместе с новой функциональностью yield from в Python 3. Верхний / принятый ответ - очень неполный ответ.

Критика ответа, предполагающая yield в выражении или понимании генератора.

Грамматика в настоящее время допускает любое выражение в понимании списка.

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

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

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

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

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

Я согласен с любым подходом. Оставить вещи такими, какие они есть в Python 3 не годится, ИМХО.

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

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

Для того, чтобы добраться туда, мы, вероятно, захотим:

  • SyntaxWarning или DeprecationWarning в 3.7
  • Предупреждение Py3k в 2.7.x
  • SyntaxError в 3.8

Ура, Ник.

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

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

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

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

В Python 2:

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

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

В Python 3:

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

Сноски

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

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

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

avatar
Will Dereham
20 мая 2015 в 06:19
49

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

avatar
Sławomir Lenart
24 июля 2014 в 21:15
148

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

yield from <expr>

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

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

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

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

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

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

avatar
Mike McKerns
4 февраля 2014 в 02:27
151

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

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

Например:

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()
00prometheus
4 декабря 2015 в 18:31
1

Милый! батут (в смысле Лиспа). Таких нечасто можно увидеть!

avatar
Engin OZTURK
20 декабря 2013 в 13:07
94

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

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

Вывод:

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

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

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

Engin OZTURK
2 июля 2018 в 01:44
0

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

avatar
alinsoar
21 августа 2013 в 19:01
123

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

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

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

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

Вот демонстрация того, как можно переписать yield, который использует структуру R6RS, но семантика идентична семантике Python. Это та же модель вычислений, и требуется только изменение синтаксиса, чтобы переписать ее с использованием yield 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
->
avatar
Evgeni Sergeev
14 июня 2013 в 16:36
78

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

avatar
aestrivex
4 апреля 2013 в 14:56
190

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

Оператор 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.

avatar
johnzachary
28 января 2013 в 01:37
108

Я собирался опубликовать «прочтите 19-ю страницу книги Бизли« Python: Essential Reference »для быстрого описания генераторов», но многие другие уже опубликовали хорошие описания.

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

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

avatar
Daniel
18 января 2013 в 17:25
266

Для тех, кто предпочитает минималистичный рабочий пример, медитируйте на этом интерактивном сеансе 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
avatar
RBansal
16 января 2013 в 06:42
215

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 для проталкивания вещей через стек вызовов вместо использования какой-либо переменной стека. Генераторы также могут использоваться для специализированного обхода дерева и всего другого.

It'sNotALie.
21 марта 2019 в 18:33
1

Просто примечание - в Python 3 range также возвращает генератор вместо списка, поэтому вы также увидите аналогичную идею, за исключением того, что __repr__ / __str__ переопределены, чтобы показать лучший результат, в этом случае range(1, 10, 2).

avatar
Dustin Getz
3 октября 2012 в 20:38
119

Вот несколько примеров 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)
avatar
ninjagecko
19 июня 2011 в 06:33
510

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

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

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

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

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

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

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

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

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

Пример

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

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

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

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

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

Пример сравнения с «просто возвращением списка»

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

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

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

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


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

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

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

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

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


За кадром

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

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

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


Детали

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

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

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

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

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

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

avatar
user28409
25 октября 2008 в 21:22
2227

Быстрый путь к пониманию yield

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

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

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

Зачем нужны генераторы?

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

DanielSank
17 июня 2017 в 22:41
27

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

Pedro
14 сентября 2017 в 14:48
11

"это может быть цикл for, но это также может быть код типа otherlist.extend(mylist)" -> Это неверно. extend() изменяет список на месте и не возвращает итерацию. Попытка зациклить otherlist.extend(mylist) не удастся с TypeError, потому что extend() неявно возвращает None, и вы не можете зациклить None.

today
26 декабря 2017 в 18:53
7

@pedro Вы неправильно поняли это предложение. Это означает, что python выполняет два упомянутых шага на mylist (не на otherlist) при выполнении otherlist.extend(mylist).

avatar
Claudiu
24 октября 2008 в 08:44
272

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

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

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

avatar
tzot
24 октября 2008 в 00:36
173

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

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

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

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

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

for item in sequence:

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

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

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

avatar
Jason Baker
23 октября 2008 в 22:28
633

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

Итератор - это просто причудливый термин для объекта, у которого есть метод 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

Есть ли в этом больше смысла или просто вас больше смущает? :)

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

jfs
25 октября 2008 в 02:03
1

__getitem__ можно определить вместо __iter__. Например: class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i), он напечатает: 0, 10, 20, ... 90

Peter
6 мая 2017 в 14:37
24

Я пробовал этот пример в Python 3.6, и если я создам iterator = some_function(), у переменной iterator больше не будет функции с именем next(), а будет только функция __next__(). Думал упомянуть об этом.

SystematicDisintegration
11 мая 2020 в 22:50
0

Где реализация цикла for, которую вы написали, вызывает метод __iter__ для iterator, созданный экземпляр it?

gioxc88
15 октября 2020 в 13:52
0

К сожалению, это совершенно неверный ответ. Это не то, что интерпретатор Python делает с генераторами. Он не создает класс, начиная с функции генератора, а реализует __iter__ и __next__. То, что он на самом деле делает под капотом, объясняется в этой публикации coderhelper.com/questions/45723893/…. Чтобы процитировать @Raymond Hettinger «генераторы не реализованы внутри, как показано в вашем чистом классе python. Вместо этого они имеют большую часть той же логики, что и обычные функции»

avatar
Jon Skeet
23 октября 2008 в 22:26
212

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

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

avatar
Douglas Mayle
23 октября 2008 в 22:24
377

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

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

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

kurosch
24 октября 2008 в 18:11
115

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