“yield”关键字是做什么用的?

时间:2023-02-07 00:43:06

What is the use of the yield keyword in Python? What does it do?

在Python中,yield关键字的使用是什么?它做什么?

For example, I'm trying to understand this code1:

例如,我试图理解这个code1:

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  

And this is the caller:

这是来电者:

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

What happens when the method _get_child_candidates is called? Is a list returned? A single element? Is it called again? When will subsequent calls stop?

调用方法_get_child_候选者时会发生什么情况?返回一个列表吗?单个元素?这叫什么来着?后续电话什么时候停止?


1. The code comes from Jochen Schulz (jrschulz), who made a great Python library for metric spaces. This is the link to the complete source: Module mspace.

1。代码来自Jochen Schulz (jrschulz),他为度量空间创建了一个伟大的Python库。这是到完整源的链接:模块mspace。

36 个解决方案

#1


11628  

To understand what yield does, you must understand what generators are. And before generators come iterables.

要了解什么是yield,您必须了解生成器是什么。在生成器出现之前。

Iterables

When you create a list, you can read its items one by one. Reading its items one by one is called iteration:

当您创建一个列表时,您可以逐个读取它的项。一个接一个地阅读它的项目叫做迭代:

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

mylist is an iterable. When you use a list comprehension, you create a list, and so an iterable:

mylist iterable。当您使用列表理解时,您将创建一个列表,因此可以迭代:

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

Everything you can use "for... in..." on is an iterable; lists, strings, files...

你可以用“for…”在…" on是可重复的;列表、字符串、文件…

These iterables are handy because you can read them as much as you wish, but you store all the values in memory and this is not always what you want when you have a lot of values.

这些iterables很方便,因为您可以随心所欲地阅读它们,但是您可以将所有的值存储在内存中,并且当您有很多值时,这并不总是您想要的。

Generators

Generators are iterators, a kind of iterable you can only iterate over once. Generators do not store all the values in memory, they generate the values on the fly:

生成器是迭代器,一种迭代器,您只能迭代一次。生成器不会将所有的值存储在内存中,它们会动态生成值:

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

It is just the same except you used () instead of []. BUT, you cannot perform for i in mygenerator a second time since generators can only be used once: they calculate 0, then forget about it and calculate 1, and end calculating 4, one by one.

它和你用的是一样的(),而不是()。但是,由于生成器只能使用一次,所以不能在mygenerator中进行第二次执行:它们计算0,然后忘记它,计算1,然后逐个计算4。

Yield

yield is a keyword that is used like return, except the function will return a generator.

yield是一个用于返回的关键字,除了函数将返回生成器。

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

Here it's a useless example, but it's handy when you know your function will return a huge set of values that you will only need to read once.

这是一个无用的例子,但是当你知道你的函数会返回一组你只需要读一次的值时,它就很方便了。

To master yield, you must understand that when you call the function, the code you have written in the function body does not run. The function only returns the generator object, this is a bit tricky :-)

要掌握yield,您必须理解,当您调用函数时,在函数体中编写的代码不会运行。函数只返回生成器对象,这有点棘手:-)

Then, your code will be run each time the for uses the generator.

然后,您的代码将在每次使用生成器时运行。

Now the hard part:

现在困难的部分:

The first time the for calls the generator object created from your function, it will run the code in your function from the beginning until it hits yield, then it'll return the first value of the loop. Then, each other call will run the loop you have written in the function one more time, and return the next value, until there is no value to return.

第一次调用从函数创建的生成器对象时,它将从开始运行到函数中的代码,直到它命中yield,然后它将返回循环的第一个值。然后,每个其他调用将运行您在函数中再次写入的循环,并返回下一个值,直到没有返回值为止。

The generator is considered empty once the function runs but does not hit yield anymore. It can be because the loop had come to an end, or because you do not satisfy an "if/else" anymore.

一旦函数运行,生成器就会被认为是空的,但它不会再命中yield。这可能是因为循环已经结束,或者因为您不再满足“if/else”。


Your code explained

Generator:

发电机:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

Caller:

打电话者:

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

This code contains several smart parts:

这段代码包含几个智能部分:

  • The loop iterates on a list but the list expands while the loop is being iterated :-) It's a concise way to go through all these nested data even if it's a bit dangerous since you can end up with an infinite loop. In this case, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) exhausts all the values of the generator, but while keeps creating new generator objects which will produce different values from the previous ones since it's not applied on the same node.

    循环在列表上迭代,但是在循环迭代时,列表会扩展:-)这是一种很简洁的方法,可以遍历所有这些嵌套的数据,即使它有点危险,因为您可以以一个无限循环结束。在这种情况下,candidates.extend(节点。_get_child_候选(distance, min_dist, max_dist))耗尽生成器的所有值,但同时继续创建新的生成器对象,该对象将产生与之前的不同的值,因为它不在同一个节点上应用。

  • The extend() method is a list object method that expects an iterable and adds its values to the list.

    extend()方法是一个列表对象方法,它期望迭代并将其值添加到列表中。

Usually we pass a list to it:

通常我们会给它一个列表:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

But in your code it gets a generator, which is good because:

但是在你的代码中它得到了一个生成器,这很好,因为:

  1. You don't need to read the values twice.
  2. 您不需要读取两次值。
  3. You may have a lot of children and you don't want them all stored in memory.
  4. 你可能有很多孩子,你不希望他们都存储在内存中。

And it works because Python does not care if the argument of a method is a list or not. Python expects iterables so it will work with strings, lists, tuples and generators! This is called duck typing and is one of the reason why Python is so cool. But this is another story, for another question...

这是因为Python并不关心方法的参数是否为列表。Python期待iterables,因此它将处理字符串、列表、元组和生成器!这就是所谓的duck typing,这也是Python之所以如此酷的原因之一。但这是另一个故事,另一个问题……

You can stop here, or read a little bit to see an advanced use of a generator:

你可以在这里停下来,或者读点东西,看看有什么先进的使用方法:

Controlling a generator exhaustion

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

Note: For Python3 useprint(corner_street_atm.__next__()) or print(next(corner_street_atm))

注意:对于Python3 useprint(_street_atm.__next__())或print(下一个(角_street_atm))

It can be useful for various things like controlling access to a resource.

它可以用于控制对资源的访问。

Itertools, your best friend

The itertools module contains special functions to manipulate iterables. Ever wish to duplicate a generator? Chain two generators? Group values in a nested list with a one liner? Map / Zip without creating another list?

itertools模块包含用于操作iterables的特殊函数。想要复制一个发电机吗?链两个发电机?嵌套列表中的组值与一行?Map / Zip而不创建另一个列表?

Then just import itertools.

然后就出现进口itertools。

An example? Let's see the possible orders of arrival for a 4 horse race:

一个例子吗?让我们来看看4匹马的到来的可能顺序:

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

Understanding the inner mechanisms of iteration

Iteration is a process implying iterables (implementing the __iter__() method) and iterators (implementing the __next__() method). Iterables are any objects you can get an iterator from. Iterators are objects that let you iterate on iterables.

迭代是一个意味着迭代的过程(实现__iter__()方法)和迭代器(实现__next__()方法)。Iterables是任何可以从一个迭代器获得的对象。迭代器是让您在iterables上迭代的对象。

More about it in this article about how for loops work.

本文将详细介绍如何循环工作。

#2


1564  

Shortcut to Grokking yield

When you see a function with yield statements, apply this easy trick to understand what will happen:

当你看到一个带有yield语句的函数时,应用这个简单的技巧来理解将要发生的事情:

  1. Insert a line result = [] at the start of the function.
  2. 在函数的开始处插入一行结果=[]。
  3. Replace each yield expr with result.append(expr).
  4. 用result.append(expr)替换每个yield expr。
  5. Insert a line return result at the bottom of the function.
  6. 在函数的底部插入一行返回结果。
  7. Yay - no more yield statements! Read and figure out code.
  8. 耶-不要再屈服了!阅读并计算代码。
  9. Compare function to original definition.
  10. 将函数与原始定义进行比较。

This trick may give you an idea of the logic behind the function, but what actually happens with yield is significantly different that what happens in the list based approach. In many cases the yield approach will be a lot more memory efficient and faster too. In other cases this trick will get you stuck in an infinite loop, even though the original function works just fine. Read on to learn more...

这个技巧可以让您了解函数背后的逻辑,但实际发生的情况与基于列表的方法有很大的不同。在许多情况下,收益率方法将会大大提高内存的效率和速度。在其他情况下,这个技巧会让你陷入无限循环,即使最初的函数很好。继续往下读,了解更多……

Don't confuse your Iterables, Iterators and Generators

First, the iterator protocol - when you write

首先,迭代器协议——当您编写时。

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

Python performs the following two steps:

Python执行以下两个步骤:

  1. Gets an iterator for mylist:

    获取mylist的迭代器:

    Call iter(mylist) -> this returns an object with a next() method (or __next__() in Python 3).

    调用iter(mylist) ->,它将以Python 3中的next()方法(或__next__())返回一个对象。

    [This is the step most people forget to tell you about]

    [这是大多数人忘记告诉你的步骤]

  2. Uses the iterator to loop over items:

    使用迭代器对项进行循环:

    Keep calling the next() method on the iterator returned from step 1. The return value from next() is assigned to x and the loop body is executed. If an exception StopIteration is raised from within next(), it means there are no more values in the iterator and the loop is exited.

    在第1步返回的迭代器上继续调用next()方法。下一个()的返回值被赋给x,然后执行循环体。如果在next()中引发异常停止迭代,则意味着迭代器中不再有值,循环退出。

The truth is Python performs the above two steps anytime it wants to loop over the contents of an object - so it could be a for loop, but it could also be code like otherlist.extend(mylist) (where otherlist is a Python list).

事实上,Python在任何时候都执行上述两个步骤,它希望对对象的内容进行循环——因此它可能是for循环,但它也可以是像otherlist.extend(mylist)那样的代码(其他列表是Python列表)。

Here mylist is an iterable because it implements the iterator protocol. In a user defined class, you can implement the __iter__() method to make instances of your class iterable. This method should return an iterator. An iterator is an object with a next() method. It is possible to implement both __iter__() and next() on the same class, and have __iter__() return self. This will work for simple cases, but not when you want two iterators looping over the same object at the same time.

这里mylist是一个可迭代的,因为它实现了迭代器协议。在用户定义的类中,您可以实现__iter__()方法,以使类的实例可以迭代。该方法应该返回一个迭代器。迭代器是带有next()方法的对象。在同一个类上实现__iter__()和next()是可能的,并且具有__iter__()返回self。这将适用于简单的情况,但不是当您希望两个迭代器同时遍历同一对象时。

So that's the iterator protocol, many objects implement this protocol:

这就是迭代器协议,很多对象实现了这个协议

  1. Built-in lists, dictionaries, tuples, sets, files.
  2. 内置列表,字典,元组,集,文件。
  3. User defined classes that implement __iter__().
  4. 用户定义的类实现__iter__()。
  5. Generators.
  6. 发电机。

Note that a for loop doesn't know what kind of object it's dealing with - it just follows the iterator protocol, and is happy to get item after item as it calls next(). Built-in lists return their items one by one, dictionaries return the keys one by one, files return the lines one by one, etc. And generators return... well that's where yield comes in:

注意,for循环不知道它要处理什么样的对象——它只遵循迭代器协议,并且很高兴在它调用next()之后获得item。内置的列表一个接一个地返回他们的项目,字典一个一个地返回键,文件一个接一个地返回,等等。这就是收益的来源:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Instead of yield statements, if you had three return statements in f123() only the first would get executed, and the function would exit. But f123() is no ordinary function. When f123() is called, it does not return any of the values in the yield statements! It returns a generator object. Also, the function does not really exit - it goes into a suspended state. When the for loop tries to loop over the generator object, the function resumes from its suspended state at the very next line after the yield it previously returned from, executes the next line of code, in this case a yield statement, and returns that as the next item. This happens until the function exits, at which point the generator raises StopIteration, and the loop exits.

如果在f123()中有三个返回语句,而不是yield语句,那么只有第一个语句会被执行,函数将退出。但是f123()不是普通的函数。当调用f123()时,它不会返回yield语句中的任何值!它返回一个生成器对象。而且,函数并没有真正退出——它进入了一个暂停状态。当for循环试图对生成器对象进行循环时,该函数从它的挂起状态恢复到下一行,在此之前,该函数会执行下一行代码,在本例中,执行下一行代码,并将其作为下一项返回。这种情况发生在函数退出之前,此时生成器将引发StopIteration,并且循环退出。

So the generator object is sort of like an adapter - at one end it exhibits the iterator protocol, by exposing __iter__() and next() methods to keep the for loop happy. At the other end however, it runs the function just enough to get the next value out of it, and puts it back in suspended mode.

因此,生成器对象有点类似于适配器——在一端,它展示了迭代器协议,通过暴露__iter__()和next()方法来保持for循环的快乐。然而,在另一端,它运行的功能刚好可以从它中获取下一个值,并将其返回到挂起模式中。

Why Use Generators?

Usually you can write code that doesn't use generators but implements the same logic. One option is to use the temporary list 'trick' I mentioned before. That will not work in all cases, for e.g. if you have infinite loops, or it may make inefficient use of memory when you have a really long list. The other approach is to implement a new iterable class SomethingIter that keeps state in instance members and performs the next logical step in it's next() (or __next__() in Python 3) method. Depending on the logic, the code inside the next() method may end up looking very complex and be prone to bugs. Here generators provide a clean and easy solution.

通常,您可以编写不使用生成器但实现相同逻辑的代码。一种选择是使用我之前提到的临时列表“技巧”。这在所有情况下都不起作用,例如,如果您有无限循环,或者当您有一个非常长的列表时,它可能会使内存使用效率低下。另一种方法是实现一个新的iterable类SomethingIter,它在实例成员中保持状态,并在它的下一个()(或__next__())方法中执行下一个逻辑步骤。根据逻辑,下一个()方法中的代码可能看起来非常复杂,并且容易出现错误。这里的生成器提供了一个干净和简单的解决方案。

#3


370  

Think of it this way:

这样想:

An iterator is just a fancy sounding term for an object that has a next() method. So a yield-ed function ends up being something like this:

迭代器对于具有next()方法的对象来说只是一个花哨的术语。所以一个屈服函数最终是这样的

Original version:

原始版本:

def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

This is basically what the python interpreter does with the above code:

这基本上就是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 more insight as to what's happening behind the scenes, the for loop can be rewritten to this:

为了更深入地了解幕后发生的事情,For循环可以改写为:

iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

Does that make more sense or just confuse you more? :)

这更有意义,还是让你更困惑?:)

EDIT: I should note that this IS an oversimplification for illustrative purposes. :)

编辑:我应该注意,这是为了说明的目的过于简化。:)

EDIT 2: Forgot to throw the StopIteration exception

编辑2:忘记抛出StopIteration异常。

#4


329  

The yield keyword is reduced to two simple facts:

yield关键字被简化为两个简单的事实:

  1. If the compiler detects the yield keyword anywhere inside a function, that function no longer returns via the return statement. Instead, it immediately returns a lazy "pending list" object called a generator
  2. 如果编译器在函数内的任何地方检测到yield关键字,则该函数不再通过返回语句返回。相反,它会立即返回一个名为生成器的惰性“挂起列表”对象。
  3. A generator is iterable. What is an iterable? It's anything like a list or set or range or dict-view, with a built-in protocol for visiting each element in a certain order.
  4. 发电机是iterable。什么是iterable ?它类似于列表或设置、范围或dict-view,它带有一个内置的协议,可以按一定的顺序访问每个元素。

In a nutshell: a generator is a lazy, incrementally-pending list, and yield statements allow you to use function notation to program the list values the generator should incrementally spit out.

简而言之:生成器是一个惰性的、递增的列表,而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]]

Example

Let's define a function makeRange that's just like Python's range. Calling makeRange(n) RETURNS A GENERATOR:

让我们定义一个函数makeRange,它就像Python的范围。调用makeRange(n)返回生成器:

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

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

To force the generator to immediately return its pending values, you can pass it into list() (just like you could any iterable):

为了强制生成器立即返回其挂起的值,您可以将其传递到list()(就像您可以迭代):

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

Comparing example to "just returning a list"

The above example can be thought of as merely creating a list which you append to and return:

上面的例子可以被认为仅仅是创建一个列表,并将其附加到并返回:

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

There is one major difference, though; see the last section.

但有一个主要区别;看到最后一节。


How you might use generators

An iterable is the last part of a list comprehension, and all generators are iterable, so they're often used like so:

迭代是列表理解的最后一部分,所有的生成器都是可迭代的,因此它们经常被使用:

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

To get a better feel for generators, you can play around with the itertools module (be sure to use chain.from_iterable rather than chain when warranted). For example, you might even use generators to implement infinitely-long lazy lists like itertools.count(). You could implement your own def enumerate(iterable): zip(count(), iterable), or alternatively do so with the yield keyword in a while-loop.

为了更好地了解生成器,您可以使用itertools模块(确保使用chain.from_iterable,而不是在必要时使用链)。例如,您甚至可以使用生成器来实现像itertools.count()这样的无限长的惰性列表。您可以实现自己的def enumerate(iterable): zip(count(), iterable),或者在while循环中使用yield关键字。

Please note: generators can actually be used for many more things, such as implementing coroutines or non-deterministic programming or other elegant things. However, the "lazy lists" viewpoint I present here is the most common use you will find.

请注意:生成器实际上可以用于更多的事情,例如实现coroutines或不确定性编程或其他优雅的东西。然而,我在这里展示的“懒惰列表”观点是您将会发现的最常见的用法。


Behind the scenes

This is how the "Python iteration protocol" works. That is, what is going on when you do list(makeRange(5)). This is what I describe earlier as a "lazy, incremental list".

这就是“Python迭代协议”的工作原理。也就是说,当你做列表的时候(makeRange(5))。这就是我之前所说的“懒惰的增量列表”。

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

The built-in function next() just calls the objects .next() function, which is a part of the "iteration protocol" and is found on all iterators. You can manually use the next() function (and other parts of the iteration protocol) to implement fancy things, usually at the expense of readability, so try to avoid doing that...

下面的内置函数()只调用对象。next()函数,它是“迭代协议”的一部分,在所有迭代器上都可以找到。您可以手动使用next()函数(以及迭代协议的其他部分)来实现一些花哨的东西,通常是以可读性为代价的,所以尽量避免这样做……


Minutiae

Normally, most people would not care about the following distinctions and probably want to stop reading here.

通常情况下,大多数人不关心下面的区别,可能想要停止阅读。

In Python-speak, an iterable is any object which "understands the concept of a for-loop" like a list [1,2,3], and an iterator is a specific instance of the requested for-loop like [1,2,3].__iter__(). A generator is exactly the same as any iterator, except for the way it was written (with function syntax).

在python语言中,iterable是任何“理解for循环”的对象,如列表[1,2,3],迭代器是请求for循环的特定实例,如[1,2,3].__iter__()。生成器与任何迭代器完全一样,只是它的编写方式(使用函数语法)除外。

When you request an iterator from a list, it creates a new iterator. However, when you request an iterator from an iterator (which you would rarely do), it just gives you a copy of itself.

当您从列表中请求迭代器时,它将创建一个新的迭代器。但是,当您从迭代器请求迭代器时(您很少会这样做),它只会给您一个自己的副本。

Thus, in the unlikely event that you are failing to do something like this...

因此,在不太可能的情况下,你不能做这样的事情……

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

... then remember that a generator is an iterator; that is, it is one-time-use. If you want to reuse it, you should call myRange(...) again. If you need to use the result twice, convert the result to a list and store it in a variable x = list(myRange(5)). Those who absolutely need to clone a generator (for example, who are doing terrifyingly hackish metaprogramming) can use itertools.tee if absolutely necessary, since the copyable iterator Python PEP standards proposal has been deferred.

…然后记住,生成器是迭代器;也就是说,它是一次性的。如果您想重用它,您应该再次调用myRange(…)。如果需要两次使用结果,请将结果转换为列表并将其存储在变量x = list(myRange(5))中。那些绝对需要克隆一个生成器的人(例如,正在做可怕的黑客元编程的人)可以使用itertools。tee如果绝对必要,因为可复制的迭代器Python PEP标准提案已经被推迟。

#5


226  

What does the yield keyword do in Python?

在Python中,yield关键字是干什么的?

Answer Outline/Summary

  • A function with yield, when called, returns a Generator.
  • 当调用一个函数时,它返回一个生成器。
  • Generators are iterators because they implement the iterator protocol, so you can iterate over them.
  • 生成器是迭代器,因为它们实现了迭代器协议,因此您可以迭代它们。
  • A generator can also be sent information, making it conceptually a coroutine.
  • 生成器也可以被发送信息,使它在概念上成为一个协同程序。
  • In Python 3, you can delegate from one generator to another in both directions with yield from.
  • 在Python 3中,您可以从一个生成器向两个方向的另一个生成器委托,并从中获得收益。
  • (Appendix critiques a couple of answers, including the top one, and discusses the use of return in a generator.)
  • (附录对几个答案进行了评论,包括最上面的一个,并讨论了在生成器中使用返回的问题。)

Generators:

yield is only legal inside of a function definition, and the inclusion of yield in a function definition makes it return a generator.

yield在函数定义中是合法的,在函数定义中包含yield使它返回生成器。

The idea for generators comes from other languages (see footnote 1) with varying implementations. In Python's Generators, the execution of the code is frozen at the point of the yield. When the generator is called (methods are discussed below) execution resumes and then freezes at the next yield.

生成生成器的想法来自于其他语言(参见脚注1),并有不同的实现。在Python的生成器中,代码的执行在yield的时候被冻结。当调用生成器时(下面将讨论方法),执行恢复,然后在下一次执行时冻结。

yield provides an easy way of implementing the iterator protocol, defined by the following two methods: __iter__ and next (Python 2) or __next__ (Python 3). Both of those methods make an object an iterator that you could type-check with the Iterator Abstract Base Class from the collections module.

yield提供了一种实现迭代器协议的简单方法,它由以下两种方法定义:__iter__和next (Python 2)或__next__ (Python 3)。

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

The generator type is a sub-type of iterator:

生成器类型是迭代器的子类型:

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

And if necessary, we can type-check like this:

如果有必要,我们可以这样检查:

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

A feature of an Iterator is that once exhausted, you can't reuse or reset it:

迭代器的一个特性是,一旦耗尽,就不能重用或重置它:

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

You'll have to make another if you want to use its functionality again (see footnote 2):

如果你想再次使用它的功能,你必须再做一个(参见脚注2):

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

One can yield data programmatically, for example:

一个可以以编程方式生成数据,例如:

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

The above simple generator is also equivalent to the below - as of Python 3.3 (and not available in Python 2), you can use yield from:

上面的简单生成器也等价于下面的Python 3.3(在Python 2中不可用),您可以使用:

def func(an_iterable):
    yield from an_iterable

However, yield from also allows for delegation to subgenerators, which will be explained in the following section on cooperative delegation with sub-coroutines.

但是,也允许向次级发电机派遣代表团,这将在下一节中解释与子协程的合作代表团。

Coroutines:

yield forms an expression that allows data to be sent into the generator (see footnote 3)

yield形成一个表达式,允许将数据发送到生成器(参见脚注3)

Here is an example, take note of the received variable, which will point to the data that is sent to the generator:

下面是一个示例,注意接收到的变量,它将指向发送给生成器的数据:

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)

First, we must queue up the generator with the builtin function, next. It will call the appropriate next or __next__ method, depending on the version of Python you are using:

首先,我们必须将生成器与builtin函数进行排队。它将调用适当的next或__next__方法,这取决于您使用的Python版本:

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

And now we can send data into the generator. (Sending None is the same as calling next.) :

现在我们可以把数据发送到生成器中。(发送没有一个和下一个是一样的。)

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

Cooperative Delegation to Sub-Coroutine with yield from

Now, recall that yield from is available in Python 3. This allows us to delegate coroutines to a subcoroutine:

现在,回想一下Python 3中可用的yield。这使得我们可以将coroutines委托给一个subcoroutine:

def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

And now we can delegate functionality to a sub-generator and it can be used by a generator just as above:

现在我们可以将功能委托给子生成器,它可以被一个生成器使用,就像上面一样:

>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

You can read more about the precise semantics of yield from in PEP 380.

您可以从PEP 380中了解更多关于yield的确切语义。

Other Methods: close and throw

The close method raises GeneratorExit at the point the function execution was frozen. This will also be called by __del__ so you can put any cleanup code where you handle the GeneratorExit:

在函数执行被冻结时,关闭方法将生成GeneratorExit。这也将被__del__调用,这样您就可以在处理GeneratorExit时使用任何清理代码:

>>> my_account.close()

You can also throw an exception which can be handled in the generator or propagated back to the user:

您还可以抛出一个异常,该异常可以在生成器中处理或向用户传播:

>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

Conclusion

I believe I have covered all aspects of the following question:

我相信我已经涵盖了以下问题的所有方面:

What does the yield keyword do in Python?

在Python中,yield关键字是干什么的?

It turns out that yield does a lot. I'm sure I could add even more thorough examples to this. If you want more or have some constructive criticism, let me know by commenting below.

结果是,产量有很大的影响,我相信我能在这上面再加上更彻底的例子。如果你想要更多或有一些建设性的批评,请通过下面的评论让我知道。


Appendix:

Critique of the Top/Accepted Answer**

  • It is confused on what makes an iterable, just using a list as an example. See my references above, but in summary: an iterable has an __iter__ method returning an iterator. An iterator provides a .next (Python 2 or .__next__ (Python 3) method, which is implicitly called by for loops until it raises StopIteration, and once it does, it will continue to do so.
  • 它混淆了什么是可迭代的,仅仅使用一个列表作为例子。参见上面的参考资料,但总结一下:iterable有一个__iter__方法返回一个迭代器。迭代器提供了一个.next (Python 2或.__next__ (Python 3)方法,它是由for循环隐式调用的,直到它产生StopIteration,一旦它完成,它将继续这样做。
  • It then uses a generator expression to describe what a generator is. Since a generator is simply a convenient way to create an iterator, it only confuses the matter, and we still have not yet gotten to the yield part.
  • 然后使用生成器表达式来描述生成器。由于生成器仅仅是创建迭代器的一种方便的方法,它只会混淆问题,而我们还没有到达yield部分。
  • In Controlling a generator exhaustion he calls the .next method, when instead he should use the builtin function, next. It would be an appropriate layer of indirection, because his code does not work in Python 3.
  • 在控制发电机耗竭时,他调用。next方法,当他应该使用builtin函数时,next。这将是一个适当的间接层,因为他的代码在Python 3中不起作用。
  • Itertools? This was not relevant to what yield does at all.
  • Itertools吗?这与收益率的作用无关。
  • No discussion of the methods that yield provides along with the new functionality yield from in Python 3. The top/accepted answer is a very incomplete answer.
  • 不讨论yield提供的方法以及Python 3中提供的新功能。答案是一个非常不完整的答案。

Critique of answer suggesting yield in a generator expression or comprehension.

The grammar currently allows any expression in a list comprehension.

语法目前允许列表理解中的任何表达式。

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

Since yield is an expression, it has been touted by some as interesting to use it in comprehensions or generator expression - in spite of citing no particularly good use-case.

由于yield是一个表达式,它被一些人吹捧为在理解或生成器表达式中使用它,尽管引用的不是特别好的用例。

The CPython core developers are discussing deprecating its allowance. Here's a relevant post from the mailing list:

CPython的核心开发人员正在讨论减少其津贴。以下是邮件列表中的相关文章:

On 30 January 2017 at 19:05, Brett Cannon wrote:

2017年1月30日19:05,Brett Cannon写道:

On Sun, 29 Jan 2017 at 16:39 Craig Rodrigues wrote:

2017年1月29日16时39分,克雷格·罗德里格斯(Craig Rodrigues)在《太阳报》上写道:

I'm OK with either approach. Leaving things the way they are in Python 3 is no good, IMHO.

这两种方法我都可以接受。在Python 3中保留它们的方式并不好,IMHO。

My vote is it be a SyntaxError since you're not getting what you expect from the syntax.

我的投票是一个SyntaxError,因为你没有得到你期望的语法。

I'd agree that's a sensible place for us to end up, as any code relying on the current behaviour is really too clever to be maintainable.

我同意这对我们来说是一个明智的选择,因为任何依赖于当前行为的代码都非常聪明,难以维护。

In terms of getting there, we'll likely want:

在到达目的地方面,我们可能会想:

  • SyntaxWarning or DeprecationWarning in 3.7
  • 3.7的SyntaxWarning或DeprecationWarning。
  • Py3k warning in 2.7.x
  • 在2.7.x Py3k警告
  • SyntaxError in 3.8
  • SyntaxError在3.8

Cheers, Nick.

干杯,尼克。

-- Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia

——澳大利亚布里斯班布里斯班的Nick Coghlan | ncoghlan。

Further, there is an outstanding issue (10544) which seems to be pointing in the direction of this never being a good idea (PyPy, a Python implementation written in Python, is already raising syntax warnings.)

此外,还有一个悬而未决的问题(10544),它似乎指向了这个问题的方向(PyPy, Python的Python实现,它已经在提高语法警告了)。

Bottom line, until the developers of CPython tell us otherwise: Don't put yield in a generator expression or comprehension.

总之,直到CPython的开发人员告诉我们:不要在生成器的表达式或理解中使用yield。

The return statement in a generator

In Python 2:

在Python中2:

In a generator function, the return statement is not allowed to include an expression_list. In that context, a bare return indicates that the generator is done and will cause StopIteration to be raised.

在生成器函数中,返回语句不允许包含expression_list。在这种情况下,一个裸返回表明生成器已经完成,并将导致停止迭代。

An expression_list is basically any number of expressions separated by commas - essentially, in Python 2, you can stop the generator with return, but you can't return a value.

expression_list基本上是由逗号分隔的任意数量的表达式——实际上,在Python 2中,可以用return停止生成器,但是不能返回值。

In Python 3:

在Python 3:

In a generator function, the return statement indicates that the generator is done and will cause StopIteration to be raised. The returned value (if any) is used as an argument to construct StopIteration and becomes the StopIteration.value attribute.

在生成器函数中,返回语句表明生成器已完成,并将导致停止迭代。返回值(如果有的话)用作构造StopIteration的参数,并成为StopIteration。价值属性。

Footnotes

  1. The languages CLU, Sather, and Icon were referenced in the proposal to introduce the concept of generators to Python. The general idea is that a function can maintain internal state and yield intermediate data points on demand by the user. This promised to be superior in performance to other approaches, including Python threading, which isn't even available on some systems.

    在向Python引入生成器概念的建议中,引用了语言、语言、语言和图标。一般的观点是,一个函数可以保持内部状态,并根据用户的需求生成中间数据点。这保证在性能上优于其他方法,包括Python线程,这在某些系统上甚至是不可用的。

  2. This means, for example, that xrange objects (range in Python 3) aren't Iterators, even though they are iterable, because they can be reused. Like lists, their __iter__ methods return iterator objects.

    例如,这意味着xrange对象(Python 3中的范围)不是迭代器,即使它们是可迭代的,因为它们可以被重用。与列表一样,它们的__iter__方法返回迭代器对象。

  3. yield was originally introduced as a statement, meaning that it could only appear at the beginning of a line in a code block. Now yield creates a yield expression. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt This change was proposed to allow a user to send data into the generator just as one might receive it. To send data, one must be able to assign it to something, and for that, a statement just won't work.

    yield最初是作为一个语句引入的,这意味着它只能出现在代码块中的一行的开头。现在,收益率创造了一个收益表达式。这一更改被建议允许用户将数据发送到生成器中,就像人们可能接收到的那样。要发送数据,一个人必须能够将其分配给某样东西,为此,一个语句将不起作用。

#6


213  

yield is just like return - it returns whatever you tell it to. The only difference is that the next time you call the function, execution starts from the last call to the yield statement.

收益就像回报一样——它会返回你告诉它的任何东西。惟一的区别是,下一次调用该函数时,执行从上次调用yield语句开始。

In the case of your code, the function get_child_candidates is acting like an iterator so that when you extend your list, it adds one element at a time to the new list.

在代码的情况下,函数get_child_候选者表现得像一个迭代器,这样当您扩展列表时,它会在新列表的时候添加一个元素。

list.extend calls an iterator until it's exhausted. In the case of the code sample you posted, it would be much clearer to just return a tuple and append that to the list.

列表。扩展调用迭代器,直到它耗尽为止。在您发布的代码示例的情况下,只需返回一个tuple并将其添加到列表中会更加清楚。

#7


174  

There's one extra thing to mention: a function that yields doesn't actually have to terminate. I've written code like this:

还有一件事需要提一下:一个函数,它的收益率实际上不需要终止。我写过这样的代码:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

Then I can use it in other code like this:

然后我可以在其他代码中使用它:

for f in fib():
    if some_condition: break
    coolfuncs(f);

It really helps simplify some problems, and makes some things easier to work with.

它确实有助于简化一些问题,并使一些事情更容易处理。

#8


144  

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

对于那些喜欢使用最小工作示例的人,请在交互式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

#9


125  

Yield gives you a generator.

产量给你一个发电机。

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

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

如您所见,在第一个case中,foo将整个列表同时保存在内存中。对于一个包含5个元素的列表来说,这没什么大不了的,但是如果你想要一个500万的列表呢?这不仅是一个巨大的内存消耗者,而且在调用该函数时还需要花费大量的时间。在第二个例子中,bar只是给你一个生成器。生成器是可迭代的——这意味着您可以在for循环中使用它,但是每个值只能被访问一次。所有的值也不会同时存储在内存中;生成器对象“记得”上次调用它时在循环中的位置——这种方式,如果您使用的是iterable(比方说)数到500亿,那么您不必同时数到500亿,并存储500亿个数字。同样,这是一个精心设计的例子,如果你真的想数到500亿的话,你可能会使用itertools。:)

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

这是生成器最简单的用例。正如您所说的,它可以用来编写高效的排列,使用yield来将事情推到调用堆栈中,而不是使用某种堆栈变量。生成器还可以用于特殊的树遍历,以及其他所有方式。

#10


117  

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

它返回一个发电机。我对Python不是特别熟悉,但我相信如果您熟悉Python,它与c#的迭代器块是一样的。

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

有一篇IBM的文章可以很好地解释它(对于Python),就我所见。

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

关键的思想是编译器/解释器/无论什么方法,只要调用者关心,他们就可以继续调用next(),并且它将保持返回值——就好像生成器方法被暂停了一样。很明显,你不能“暂停”一个方法,所以编译器会建立一个状态机来让你记住你当前的位置,以及局部变量是什么样子。这比自己编写迭代器要容易得多。

#11


115  

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

有一种回答,我觉得还没有给出,在许多描述如何使用发电机的伟大答案中。这是PL理论的答案:

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

python中的yield语句返回一个生成器。python中的生成器是一个返回延续的函数(特别是一种类型的coroutine,但是continuations表示更通用的机制来理解正在发生的事情)。

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

在编程语言理论中,延续是一种更基本的计算方法,但它们并不经常被使用,因为它们非常难于推理,而且很难实现。但是,延续的概念很简单:它是尚未完成的计算的状态。在这个状态中,保存了变量的当前值和尚未执行的操作,等等。然后在程序的某个时候,可以调用continuation,这样程序的变量就会被重置为那个状态,而保存的操作就会被执行。

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

在这种更通用的形式中,延续可以通过两种方式实现。在调用/cc方式中,程序的堆栈实际上是保存的,然后在调用continuation时,将恢复堆栈。

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

在延续传递样式(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 )

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

在这个(非常简单)的例子中,程序员将实际编写文件的操作保存为continuation(这可能是一个非常复杂的操作,有许多细节需要写出来),然后通过这个延续(i。e,作为一个一流的闭包)给另一个操作员,它做了更多的处理,然后在必要时调用它。(我在实际的GUI编程中经常使用这种设计模式,因为它节省了代码行,或者更重要的是,在GUI事件触发后管理控制流)

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

这篇文章的其余部分将不会失去一般性,将延续作为CPS,因为它更容易理解和阅读。


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

现在让我们讨论一下python中的生成器。生成器是延续的特定子类型。然而,continuations通常可以保存计算的状态(也就是)。程序的调用栈),生成器只能通过迭代器来保存迭代的状态。尽管,这一定义对于某些用例生成器有轻微的误导。例如:

def f():
  while True:
    yield 4

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

这显然是一个合理的迭代,它的行为是很好的定义的——每次生成器遍历它时,它返回4(并且永远这样做)。但是,当迭代器的思想(即,迭代器)时,它可能不是最典型的迭代类型。,for x in collection: do_something(x))。这个例子说明了生成器的力量:如果任何东西都是迭代器,那么生成器可以保存它的迭代状态。

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

重申:Continuations可以保存程序堆栈的状态,而生成器可以保存迭代的状态。这意味着延续比生成器更强大,但也更容易生成生成器。它们对于语言设计人员来说更容易实现,而且它们对程序员来说更容易使用(如果您有一些时间可以使用,请尝试阅读并理解关于continuations和call/cc的这一页)。

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

但是您可以轻松地实现(和概念化)生成器作为一个简单的、特定的延续传递样式的例子:

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

无论何时调用yield,它都会告诉函数返回一个延续。当函数再次被调用时,它从它停止的地方开始。生成器的下一种方法基本上是:

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

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

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

这里的yield关键字是真正的生成器函数的语法糖,基本上是这样的:

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

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

请记住,这只是伪代码,而python中生成器的实际实现更为复杂。但是作为一种理解正在发生的事情的练习,尝试使用延续传递样式来实现生成器对象,而不使用yield关键字。

#12


109  

TL;DR

博士TL;

When you find yourself building a list from scratch...

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

...yield each piece instead

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

This was my first "aha" moment with yield.

这是我第一次收获“啊哈”时刻。


yield is a sugary way to say

屈服是一种甜蜜的说法。

build a series of stuff

建立一系列的东西。

Same behavior:

同样的行为:

>>> for square in squares_list(4):
...     print(square)
...
0
1
4
9
>>> for square in squares_the_yield_way(4):
...     print(square)
...
0
1
4
9

Different behavior:

不同的行为:

Yield is single-pass: you can only iterate through once. When a function has a yield in it we call it a generator function. And an iterator is what it returns. That's revealing. We lose the convenience of a container, but gain the power of an arbitrarily long series.

收益率是单通道的:您只能遍历一次。当一个函数有一个收益率时,我们称它为生成器函数。迭代器就是它返回的内容。这是揭示。我们失去了一个容器的便利,但却获得了一个任意长的系列的力量。

Yield is lazy, it puts off computation. A function with a yield in it doesn't actually execute at all when you call it. The iterator object it returns uses magic to maintain the function's internal context. Each time you call next() on the iterator (this happens in a for-loop) execution inches forward to the next yield. (return raises StopIteration and ends the series.)

收益是懒惰的,它会推迟计算。一个具有收益率的函数在调用它时实际上根本不会执行。它返回的迭代器对象使用魔法来维护函数的内部上下文。每次在迭代器上调用next(在for循环中发生)时,就会向前移动到下一个yield。(return增加了StopIteration并结束了这个系列。)

Yield is versatile. It can do infinite loops:

收益率是多才多艺的。它可以做无限循环:

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

If you need multiple passes and the series isn't too long, just call list() on it:

如果您需要多个传递,并且这个系列不太长,只需调用列表():

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

Brilliant choice of the word yield because both meanings apply:

“收益”的明智选择,因为这两种含义都适用:

yield — produce or provide (as in agriculture)

产量-生产或供应(如农业)

...provide the next data in the series.

…提供该系列中的下一个数据。

yield — give way or relinquish (as in political power)

屈服-让步或放弃(如在*中)

...relinquish CPU execution until the iterator advances.

…在迭代器前进之前放弃CPU执行。

#13


99  

An example in plain language. I will provide a correspondence between high-level human concepts to low-level python concepts.

简单语言的一个例子。我将提供高级的人类概念与低级python概念之间的对应关系。

I want to operate on a sequence of numbers, but I don't want to bother my self with the creation of that sequence, I want only to focus on the operation I want to do. So, I do the following:

我想要操作一个数字序列,但是我不想用那个序列的创建来打扰我自己,我只想专注于我想要做的操作。所以,我做如下工作:

  • I call you and tell you that I want a sequence of numbers which is produced in a specific way, and I let you know what the algorithm is.
    This step corresponds to defining the generator function, i.e. the function containing a yield.
  • 我给你打电话,告诉你我想要一个以特定的方式产生的数字序列,我让你知道这个算法是什么。此步骤对应于定义生成器函数,即包含yield的函数。
  • Sometime later, I tell you, "ok, get ready to tell me the sequence of numbers".
    This step corresponds to calling the generator function which returns a generator object. Note that you don't tell me any numbers yet, you just grab your paper and pencil.
  • 后来,我告诉你,“好吧,准备告诉我数字的顺序”。此步骤对应于调用返回生成器对象的生成器函数。注意,你还没有告诉我任何数字,你只要拿起纸和笔就行了。
  • I ask you, "tell me the next number", and you tell me the first number; after that, you wait for me to ask you for the next number. It's your job to remember where you were, what numbers you have already said, what is the next number. I don't care about the details.
    This step corresponds to calling .next() on the generator object.
  • 我问你,“告诉我下一个数字”,你告诉我第一个数字;在那之后,你等着我问你下一个号码。你的工作就是记住你在哪里,你已经说过什么数字,下一个数字是什么。我不在乎细节。此步骤对应于在生成器对象上调用.next()。
  • … repeat previous step, until…
  • 重复前面的步骤,直到…
  • eventually, you might come to an end. You don't tell me a number, you just shout, "hold your horses! I'm done! No more numbers!"
    This step corresponds to the generator object ending its job, and raising a StopIteration exception The generator function does not need to raise the exception, it's raised automatically when the function ends or issues a return.
  • 最终,你可能会走到尽头。你不告诉我一个数字,你就喊:“住手!”我完成了!没有更多的数字!”此步骤对应于结束其作业的生成器对象,并引发一个StopIteration异常,生成器函数不需要引发异常,当函数结束或返回时,它会自动被提升。

This is what a generator does (a function that contains a yield); it starts executing, pauses whenever it does a yield, and when asked for a .next() value it continues from the point it was last. It fits perfectly by design with the iterator protocol of python, which describes how to sequentially request for values.

这是一个生成器所做的(一个包含yield的函数);它开始执行,每当它执行一个yield时暂停,当被请求一个。next()值时,它从最后一个点继续。它完全符合python的迭代器协议的设计,它描述了如何顺序地请求值。

The most famous user of the iterator protocol is the for command in python. So, whenever you do a:

迭代器协议最著名的用户是python中的for命令。所以,当你做a:

for item in sequence:

it doesn't matter if sequence is a list, a string, a dictionary or a generator object like described above; the result is the same: you read items off a sequence one by one.

无论序列是列表、字符串、字典还是像上面描述的生成器对象,都不重要;结果是一样的:您逐个读取一个序列中的项。

Note that defining a function which contains a yield keyword is not the only way to create a generator; it's just the easiest way to create one.

注意,定义包含yield关键字的函数并不是创建生成器的唯一方法;这是最简单的方法。

For more accurate information, read about iterator types, the yield statement and generators in the Python documentation.

要获得更准确的信息,请阅读Python文档中的迭代器类型、yield语句和生成器。

#14


92  

While a lot of answers show why you'd use a yield to create a generator, there are more uses for yield. It's quite easy to make a coroutine, which enables the passing of information between two blocks of code. I won't repeat any of the fine examples that have already been given about using yield to create a generator.

虽然有很多答案说明了为什么要使用yield来创建生成器,但是对于yield有更多的用途。创建一个coroutine是相当容易的,它支持在两个代码块之间传递信息。我不会重复已经给出的关于使用yield来创建生成器的优秀示例。

To help understand what a yield does in the following code, you can use your finger to trace the cycle through any code that has a yield. Every time your finger hits the yield, you have to wait for a next or a send to be entered. When a next is called, you trace through the code until you hit the yield… the code on the right of the yield is evaluated and returned to the caller… then you wait. When next is called again, you perform another loop through the code. However, you'll note that in a coroutine, yield can also be used with a send… which will send a value from the caller into the yielding function. If a send is given, then yield receives the value sent, and spits it out the left hand side… then the trace through the code progresses until you hit the yield again (returning the value at the end, as if next was called).

为了帮助理解在以下代码中收益率的作用,您可以使用您的手指在任何有收益的代码中跟踪循环。每次当你的手指按下产量时,你必须等待下一个或一个发送进入。当调用next时,您可以在代码中跟踪代码,直到到达yield……在yield的右侧的代码被评估并返回给调用者,然后等待。当再次调用next时,您将通过代码执行另一个循环。但是,您会注意到,在coroutine中,yield也可以用于发送,这将从调用者发送一个值到生成函数中。如果发送了一个send,那么yield就会接收到发送的值,然后将它从左边发送出去……然后通过代码的跟踪直到您再次命中yield(最后返回值,就像下一个被调用的那样)。

For example:

例如:

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

#15


80  

There is another yield use and meaning (since python 3.3):

还有另一个yield用法和含义(因为python 3.3):

yield from <expr>

http://www.python.org/dev/peps/pep-0380/

http://www.python.org/dev/peps/pep-0380/

A syntax is proposed for a generator to delegate part of its operations to another generator. This allows a section of code containing 'yield' to be factored out and placed in another generator. Additionally, the subgenerator is allowed to return with a value, and the value is made available to the delegating generator.

提出了一种语法,用于生成器将其部分操作委托给另一个生成器。这允许将包含“yield”的代码段分解并放置到另一个生成器中。此外,还允许子生成器以一个值返回,并将值提供给委托生成器。

The new syntax also opens up some opportunities for optimisation when one generator re-yields values produced by another.

新语法也为优化提供了一些机会,当一个生成器重新生成另一个生成器生成的值时。

moreover this will introduce (since python 3.5):

此外,这将引入(自python 3.5):

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

to avoid coroutines confused with regular generator (today yield is used in both).

为了避免与普通的发电机产生混淆(今天的产量在两者中都使用)。

#16


74  

I was going to post "read page 19 of Beazley's 'Python: Essential Reference' for a quick description of generators", but so many others have posted good descriptions already.

我将发表“Beazley’s Python的第19页:对生成器的快速描述的基本参考”,但是其他许多人已经发布了很好的描述。

Also, note that yield can be used in coroutines as the dual of their use in generator functions. Although it isn't the same use as your code snippet, (yield) can be used as an expression in a function. When a caller sends a value to the method using the send() method, then the coroutine will execute until the next (yield) statement is encountered.

此外,请注意,产量可以作为它们在发电机功能中使用的双重用途,在coroutines中使用。尽管它与您的代码片段不一样,但(yield)可以作为函数中的表达式。当调用者使用send()方法向方法发送一个值时,将执行coroutine,直到遇到下一个(yield)语句。

Generators and coroutines are a cool way to set up data-flow type applications. I thought it would be worthwhile knowing about the other use of the yield statement in functions.

生成器和coroutines是设置数据流类型应用程序的一种很酷的方法。我想知道在函数中使用yield语句的其他用法是值得的。

#17


71  

Here are some Python examples of how to actually implement generators as if Python did not provide syntactic sugar for them:

下面是一些关于如何实际实现生成器的Python示例,就像Python没有为它们提供语法糖一样:

As a Python generator:

作为一个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))

Using lexical closures instead of generators

使用词法闭包代替生成器。

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)

Using object closures instead of generators (because ClosuresAndObjectsAreEquivalent)

使用对象闭包代替生成器(因为closuresandobjectsare等效)

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)

#18


66  

From a programming viewpoint, the iterators are implemented as thunks

从编程的观点来看,迭代器是作为thunks实现的。

http://en.wikipedia.org/wiki/Thunk_(functional_programming)

http://en.wikipedia.org/wiki/Thunk_(functional_programming)

To implement iterators/generators/thread pools for concurrent execution/etc as thunks (also called anonymous functions), one uses messages sent to a closure object, which has a dispatcher, and the dispatcher answers to "messages".

为了实现并行执行/etc(也称为匿名函数)的迭代器/生成器/线程池,一个使用发送到闭包对象的消息,其中有一个dispatcher,以及dispatcher对“消息”的应答。

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

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

"next" is a message sent to a closure, created by "iter" call.

“下一个”是发送到闭包的消息,由“iter”调用创建。

There are lots of ways to implement this computation. I used mutation but it is easy to do it without mutation, by returning the current value and the next yielder.

有很多方法可以实现这个计算。我使用了突变,但很容易做到,没有突变,通过返回当前值和下一个屈服点。

Here is a demonstration which uses the structure of R6RS but the semantics is absolutely identical as in python, it's the same model of computation, only a change in syntax is required to rewrite it in python.

这里有一个使用R6RS结构的演示,但是语义与python完全相同,它是相同的计算模型,只需修改语法就可以用python重写它。

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
-> 

#19


60  

Here is a simple example:

这里有一个简单的例子:

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)   

output :

输出:

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

I am not a Python developer, but it looks to me yield holds the position of program flow and the next loop start from "yield" position. It seems like it is waiting at that position, and just before that, returning a value outside, and next time continues to work.

我不是一个Python开发人员,但它看起来对我来说是程序流的位置,下一个循环从“yield”位置开始。它似乎在等待那个位置,在此之前,返回一个值,然后下一次继续工作。

Seems to me an interesting and nice ability :D

在我看来,这是一个有趣而又美好的能力:D。

#20


53  

Here is a mental image of what yield does.

这是一个关于收益率的心理图像。

I like to think of a thread as having a stack (even when it's not implemented that way).

我喜欢把线程看作是有堆栈的(即使它不是那样实现的)。

When a normal function is called, it puts its local variables on the stack, does some computation, then clears the stack and returns. The values of its local variables are never seen again.

当调用一个正常函数时,它将本地变量放在堆栈上,进行一些计算,然后清除堆栈并返回。它的局部变量的值再也不会出现。

With a yield function, when its code begins to run (i.e. after the function is called, returning a generator object, whose next() method is then invoked), it similarly puts its local variables onto the stack and computes for a while. But then, when it hits the yield statement, before clearing its part of the stack and returning, it takes a snapshot of its local variables and stores them in the generator object. It also writes down the place where it's currently up to in its code (i.e. the particular yield statement).

使用yield函数,当代码开始运行时(即调用函数后,返回一个生成器对象,然后调用其下一个()方法),它同样将其本地变量放到堆栈上并计算一段时间。但是,当它到达yield语句时,在清理堆栈的一部分并返回之前,它会对本地变量进行快照,并将它们存储在生成器对象中。它还会写下当前代码(即特定的yield语句)的位置。

So it's a kind of a frozen function that the generator is hanging onto.

这是一个冻结的函数。

When next() is called subsequently, it retrieves the function's belongings onto the stack and re-animates it. The function continues to compute from where it left off, oblivious to the fact that it had just spent an eternity in cold storage.

当next()随后被调用时,它将检索该函数的所有属性到堆栈上并重新激活它。这个函数继续从它停止的地方进行计算,忘记了它刚刚在冷库中度过了一个永恒。

Compare the following examples:

比较下面的例子:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

When we call the second function, it behaves very differently to the first. The yield statement might be unreachable, but if it's present anywhere, it changes the nature of what we're dealing with.

当我们调用第二个函数时,它和第一个函数有很大的不同。yield语句可能是不可访问的,但是如果它存在于任何地方,它会改变我们处理的内容的性质。

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

Calling yielderFunction() doesn't run its code, but makes a generator out of the code. (Maybe it's a good idea to name such things with the yielder prefix for readability.)

调用producderfunction()并不是运行它的代码,而是从代码中生成一个生成器。(也许用让步的前缀来命名这些东西是一个好主意。)

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

The gi_code and gi_frame fields are where the frozen state is stored. Exploring them with dir(..), we can confirm that our mental model above is credible.

gi_code和gi_frame字段是存储冻结状态的地方。用dir(..)来探索它们,我们可以确认上面的心智模型是可信的。

#21


40  

Like every answer suggests, yield is used for creating a sequence generator. It's used for generating some sequence dynamically. Eg. While reading a file line by line on a network, you can use the yield function as follows:

就像所有的答案一样,yield用于创建序列生成器。它用于动态生成一些序列。如。在网络上逐行读取文件时,可以使用yield函数如下:

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

You can use it in your code as follows :

你可以在你的代码中使用它:

for line in getNextLines():
    doSomeThing(line)

Execution Control Transfer gotcha

执行控制转移问题

The execution control will be transferred from getNextLines() to the for loop when yield is executed. Thus, every time getNextLines() is invoked, execution begins from the point where it was paused last time.

执行控制将从getNextLines()转移到执行yield时的for循环。因此,每次调用getNextLines()时,执行从上次暂停的点开始。

Thus in short, a function with the following code

因此,简而言之,一个具有以下代码的函数。

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

will print

将打印

"first time"
"second time"
"third time"
"Now some useful value 12"

I hope this helps you.

我希望这能帮到你。

#22


35  

Yield is an Object

收益率是一个对象

A return in a function will return a single value.

函数的返回值将返回一个值。

If you want function to return huge set of values use yield.

如果您希望函数返回大量的值,则使用yield。

More importantly, yield is a barrier

更重要的是,收益率是一个障碍。

like Barrier in Cuda Language, it will not transfer control until it gets completed.

就像Cuda语言中的障碍一样,它在完成之前不会转移控制。

i.e

It will run the code in your function from the beginning until it hits yield. Then, it’ll return the first value of the loop. Then, every other call will run the loop you have written in the function one more time, returning the next value until there is no value to return.

它将从一开始就运行函数中的代码,直到它命中yield。然后,它将返回循环的第一个值。然后,所有其他调用将运行您在函数中再次写入的循环,返回下一个值,直到没有返回值为止。

#23


33  

yield is like a return element for a function. The difference is, that the yield element turns a function into a generator. A generator behaves just like a function until something is 'yielded'. The generator stops until it is next called, and continues from exactly the same point as it started. You can get a sequence of all the 'yielded' values in one, by calling list(generator()).

yield是函数的返回元素。不同之处在于,yield元素将一个函数变成了一个生成器。生成器的行为就像一个函数,直到某个东西被“屈服”为止。生成器将停止,直到它被调用,并且从它开始的完全相同的点继续。通过调用list(生成器()),可以在其中一个中获得所有“生成”值的序列。

#24


33  

(My below answer only speaks from the perspective of using Python generator, not the underlying implementation of generator mechanism, which involves some tricks of stack and heap manipulation.)

(下面的回答只从使用Python生成器的角度进行,而不是生成器机制的底层实现,它涉及一些堆栈和堆操作的技巧。)

When yield is used instead of a return in a python function, that function is turned into something special called generator function. That function will return an object of generator type. The yield keyword is a flag to notify the python compiler to treat such function specially. Normal functions will terminate once some value is returned from it. But with the help of the compiler, the generator function can be thought of as resumable. That is, the execution context will be restored and the execution will continue from last run. Until you explicitly call return, which will raise a StopIteration exception (which is also part of the iterator protocol), or reach the end of the function. I found a lot of references about generator but this one from the functional programming perspective is the most digestable.

当使用yield而不是python函数的返回时,该函数被转换为一个特殊的称为生成器函数。该函数将返回生成器类型的对象。yield关键字是通知python编译器专门处理该函数的标志。当某个值从它返回时,正常函数将终止。但是在编译器的帮助下,可以认为生成器函数是可恢复的。也就是说,执行上下文将被恢复,执行将从上次运行继续。在您显式地调用return之前,它将引发一个StopIteration异常(它也是迭代器协议的一部分),或者到达函数的末尾。我发现了很多关于生成器的引用,但是从函数式编程的角度来看,这是最易于消化的。

(Now I want to talk about the rationale behind generator, and the iterator based on my own understanding. I hope this can help you grasp the essential motivation of iterator and generator. Such concept shows up in other languages as well such as C#.)

现在我想谈谈生成器背后的基本原理,以及基于我自己的理解的迭代器。我希望这能帮助您掌握迭代器和生成器的基本动机。这样的概念在其他语言中也会出现,比如c#。

As I understand, when we want to process a bunch of data, we usually first store the data somewhere and then process it one by one. But this intuitive approach is problematic. If the data volume is huge, it's expensive to store them as a whole beforehand. So instead of storing the data itself directly, why not store some kind of metadata indirectly, i.e. the logic how the data is computed.

正如我所理解的,当我们想要处理一堆数据时,通常先将数据存储在某个地方,然后逐个处理。但是这种直观的方法是有问题的。如果数据量是巨大的,那么预先存储它们是很昂贵的。因此,与其直接存储数据本身,为什么不直接存储某种元数据,即如何计算数据的逻辑。

There are 2 approaches to wrap such metadata.

有两种方法来包装这些元数据。

  1. The OO approach, we wrap the metadata as a class. This is the so-called iterator who implements the iterator protocol (i.e. the __next__(), and __iter__() methods). This is also the commonly seen iterator design pattern.
  2. 面向对象方法,我们将元数据打包为一个类。这就是所谓的迭代器,它实现了迭代器协议(即__next__()和__iter__()方法)。这也是常见的迭代器设计模式。
  3. The functional approach, we wrap the metadata as a function. This is the so-called generator function. But under the hood, the returned generator object still IS-A iterator because it also implements the iterator protocol.
  4. 函数方法,我们将元数据包装成函数。这就是所谓的发电机功能。但是在引擎盖下,返回的生成器对象仍然是- a迭代器,因为它还实现了迭代器协议。

Either way, an iterator is created, i.e. some object that can give you the data you want. The OO approach may be a bit complex. Anyway, which one to use is up to you.

无论哪种方式,都创建了一个迭代器,即一些可以提供所需数据的对象。OO方法可能有点复杂。不管怎样,用哪一个取决于你。

#25


30  

The yield keyword simply collects returning results. Think of yield like return +=

yield关键字只是收集返回的结果。考虑收益率,比如return +=。

#26


29  

All great answers whereas a bit difficult for newbies.

所有伟大的答案,而对新手来说有点困难。

I assume you have learned return statement.
As an analogy, return and yield are twins.
return means 'Return and Stop' whereas 'yield` means 'Return but Continue'

我想你们已经学过了return语句。作为一个类比,回报和收益是双胞胎。return是" return and Stop "而" yield "的意思是" return but Continue "

  1. Try to get a num_list with return.
  2. 尝试获得一个返回的num_list。
def num_list(n):
    for i in range(n):
        return i

Run it:

运行该程序:

In [5]: num_list(3)
Out[5]: 0

See, you get only a single number instead of a list of them,. return never allow you happy to prevail. It implemented once and quit.

看,你只得到一个数字而不是一个列表。回报永远不会让你快乐。它实现了一次并退出了。

  1. There comes yield
  2. 有产量

Replace return with 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]

Now, you win to get all the numbers.
Comparing to return which runs once and stops, yield runs times you planed.
You can interpret return as return one of them,
yield as return all of them. This is called iterable.

现在,你赢了所有的数字。与运行一次和停止的返回相比,您计划的收益运行时间。你可以解释return作为回报其中之一,收益率作为回报。这就是所谓的iterable。

  1. One more step we can rewrite yield statement with return
  2. 还有一步,我们可以用return重写yield语句。
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]

It's the core about yield.

这是产量的核心。

The difference between a list return outputs and the object yield output is:
You can get [0, 1, 2] from a list object always whereas can only retrieve them from 'the object yield output' once.
So, it has a new name generator object as displayed in Out[11]: <generator object num_list at 0x10327c990>.

列表返回输出和对象输出结果之间的区别是:您可以从列表对象中获取[0,1,2],而只能从“对象产量输出”中获得一次。因此,它有一个新的名称生成器对象,显示在Out[11]:

In conclusion as a metaphor to grok it,

总之,作为一个隐喻,

return and yield are twins,
list and generator are twins.

return和yield是双胞胎,list和generator是双胞胎。

#27


27  

Here's a simple yield based approach, to compute the fibonacci series, explained:

这里有一个简单的基于收益的方法,来计算斐波那契数列,解释如下:

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

When you enter this into your REPL and then try and call it, you'll get a mystifying result:

当你把它输入你的REPL,然后尝试调用它,你会得到一个神秘的结果:

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

This is because the presence of yield signaled to Python that you want to create a generator, that is, an object that generates values on demand.

这是因为yield对Python发出了信号,您希望创建一个生成器,即根据需要生成值的对象。

So, how do you generate these values? This can either be done directly by using the built-in function next, or, indirectly by feeding it to a construct that consumes values.

那么,如何生成这些值呢?这可以通过使用内置函数进行直接操作,也可以通过将其提供给一个使用值的构造来间接完成。

Using the built-in next() function, you directly invoke .next/__next__, forcing the generator to produce a value:

使用内置的next()函数,您可以直接调用.next/__next__,迫使生成器产生一个值:

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

Indirectly, if you provide fib to a for loop, a list initializer, a tuple initializer, or anything else that expects an object that generates/produces values, you'll "consume" the generator until no more values can be produced by it (and it returns):

间接地,如果您将fib提供给for循环,一个列表初始化器,一个tuple初始化器,或者其他任何期望一个产生/产生值的对象,您将“消耗”生成器,直到它不再产生更多的值(并且它返回):

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

Similarly, with a tuple initializer:

类似地,使用tuple初始化器:

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

A generator differs from a function in the sense that it is lazy. It accomplishes this by maintaining it's local state and allowing you to resume whenever you need to.

生成器与函数的不同之处在于它是懒惰的。它通过维护本地状态来实现这一点,并允许您在需要时恢复。

When you first invoke fib by calling it:

当您第一次调用fib时,调用它:

f = fib()

Python compiles the function, encounters the yield keyword and simply returns a generator object back at you. Not very helpful it seems.

Python编译函数,遇到yield关键字,然后简单地返回一个生成器对象。似乎没有多大帮助。

When you then request it generates the first value, directly or indirectly, it executes all statements that it finds, until it encounters a yield, it then yields back the value you supplied to yield and pauses. For an example that better demonstrates this, let's use some print calls (replace with print "text" if on Python 2):

当您请求它生成第一个值时,它会直接或间接地执行它所找到的所有语句,直到它遇到一个yield,然后它会返回您提供给yield和停顿的值。为了更好地演示这一点,让我们使用一些打印调用(如果在Python 2中,用print“text”替换):

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

Now, enter in the REPL:

现在,进入REPL:

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

you have a generator object now waiting for a command for it to generate a value. Use next and see what get's printed:

您现在有一个生成器对象,等待它生成一个值的命令。使用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!'

The unquoted results are what's printed. The quoted result is what is returned from yield. Call next again now:

未引用的结果是打印出来的。所引用的结果是由yield返回的结果。现在再次调用下一个:

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

The generator remembers it was paused at yield value and resumes from there. The next message is printed and the search for the yield statement to pause at it performed again (due to the while loop).

生成器记得它是在yield值和从那里恢复的时候暂停的。下一个消息被打印出来,并且搜索yield语句以在它再次执行时暂停(由于while循环)。

#28


26  

Many people use return rather than yield but in some cases yield can be more efficient and easier to work with.

许多人使用回报而不是收益,但在某些情况下,收益可以更有效和更容易使用。

Here is an example which yield is definitely best for:

这里有一个例子,绝对是最好的:

return (in function)

返回(函数)

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

yield (in function)

收益率(函数)

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

Calling functions

调用函数

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)

Both functions do the same thing but yield uses 3 lines instead of 5 and has one less variable to worry about.

两个函数都做同样的事情,但是收益率用3行而不是5,并且有一个更少的变量需要担心。

This is the result from the code:

这是代码的结果:

“yield”关键字是做什么用的?

As you can see both functions do the same thing, the only difference is return_dates() gives a list and yield_dates() gives a generator

当您看到两个函数都做相同的事情时,唯一的区别是return_dates()给出了一个列表,而producd_date()提供了一个生成器。

A real life example would be something like reading a file line by line or if you just want to make a generator

一个真实的例子就像是逐行读取一个文件,或者你只是想做一个生成器。

#29


25  

In summary, the yield statement transforms your function into a factory that produces a special object called a generator which wraps around the body of your original function. When the generator is iterated, it executes your function until it reaches the next yield then suspends execution and evaluates to the value passed to yield. It repeats this process on each iteration until the path of execution exits the function. For instance;

综上所述,yield语句将您的函数转换为一个工厂,该工厂产生一个称为生成器的特殊对象,该对象包围您原始函数的主体。在迭代生成器时,它将执行您的函数,直到它到达下一个yield,然后暂停执行并计算传递给yield的值。它在每次迭代中重复这个过程,直到执行路径退出该函数。例如;

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

for i in simple_generator():
    print i

simply outputs ;

简单的输出;

one
two
three

The power comes from using the generator with a loop that calculates a sequence, the generator executes the loop stopping each time to 'yield' the next result of the calculation, in this way it calculates a list on the fly, the benefit being the memory saved for especially large calculations

功率来自于使用一个循环来计算一个序列,生成器执行循环停止每一次以“产生”计算的下一个结果,以这种方式计算一个列表在苍蝇上,好处是为特别大的计算节省的内存。

Say you wanted to create a your own range function that produces an iterable range of numbers, you could do it like so,

假设你想创建一个你自己的range函数它产生一个可重复的数字范围,你可以这样做,

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

and use it like this;

像这样使用;

for i in myRangeNaive(10):
    print i

but this is ineffecient because

但这是无效的,因为。

  • You create an array that you only use once (this wastes memory)
  • 创建一个只使用一次的数组(这浪费内存)
  • This code actually loops over that array twice! :(
  • 这个代码实际上会循环遍历该数组两次!:(

Luckily Guido and his team were generous enough to develop generators so we could just do this;

幸运的是,圭多和他的团队非常慷慨地开发了发电机,所以我们可以这样做;

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

for i in myRangeSmart(10):
    print i

Now upon each iteration a function on the generator called next() executes the function until it either reaches a 'yield' statement in which it stops and 'yields' the value or reaches the end of the function. In this case on the first call, next() executes up to the yield statement and yield 'n', on the next call it will execute the increment statement, jump back to the 'while', evaluate it, and if true, it will stop and yield 'n' again, it will continue that way until the while condition returns false and the generator jumps to the end of the function.

现在,在每个迭代上,生成器调用next()函数执行该函数,直到它到达“yield”语句为止,该语句停止并“生成”值或到达函数的末尾。在这种情况下,在第一次调用时,()执行声明收益率和收益率“n”,在下一个叫它将执行增量声明,跳回“同时”,评估,和如果这是真的,它将停止和产量再次“n”,将继续,直到条件返回false和发电机跳到函数的结束。

#30


18  

Yet another TL;DR

另一个TL,博士

iterator on list: next() returns the next element of the list

列表中的迭代器:next()返回列表的下一个元素。

iterator generator: next() will compute the next element on the fly (execute code)

迭代器生成器:下一个()将计算苍蝇的下一个元素(执行代码)

You can see the yield/generator as a way to manually run the control flow from outside (like continue loop 1 step), by calling next, however complex the flow.

您可以将yield/generator视为手动运行外部控制流的一种方式(如继续循环1步),通过调用next,不管流程多么复杂。

NOTE: the generator is NOT a normal function, it remembers previous state like local variables (stack), see other answers or articles for detailed explanation, the generator can only be iterated on once. You could do without yield but it would not be as nice, so it can be considered 'very nice' language sugar.

注意:生成器不是一个正常的函数,它会记住以前的状态,比如本地变量(stack),查看其他的答案或文章来进行详细的解释,生成器只能一次迭代。你可以不屈服,但它不会那么好,所以它可以被认为是“非常好的”语言糖。

#1


11628  

To understand what yield does, you must understand what generators are. And before generators come iterables.

要了解什么是yield,您必须了解生成器是什么。在生成器出现之前。

Iterables

When you create a list, you can read its items one by one. Reading its items one by one is called iteration:

当您创建一个列表时,您可以逐个读取它的项。一个接一个地阅读它的项目叫做迭代:

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

mylist is an iterable. When you use a list comprehension, you create a list, and so an iterable:

mylist iterable。当您使用列表理解时,您将创建一个列表,因此可以迭代:

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

Everything you can use "for... in..." on is an iterable; lists, strings, files...

你可以用“for…”在…" on是可重复的;列表、字符串、文件…

These iterables are handy because you can read them as much as you wish, but you store all the values in memory and this is not always what you want when you have a lot of values.

这些iterables很方便,因为您可以随心所欲地阅读它们,但是您可以将所有的值存储在内存中,并且当您有很多值时,这并不总是您想要的。

Generators

Generators are iterators, a kind of iterable you can only iterate over once. Generators do not store all the values in memory, they generate the values on the fly:

生成器是迭代器,一种迭代器,您只能迭代一次。生成器不会将所有的值存储在内存中,它们会动态生成值:

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

It is just the same except you used () instead of []. BUT, you cannot perform for i in mygenerator a second time since generators can only be used once: they calculate 0, then forget about it and calculate 1, and end calculating 4, one by one.

它和你用的是一样的(),而不是()。但是,由于生成器只能使用一次,所以不能在mygenerator中进行第二次执行:它们计算0,然后忘记它,计算1,然后逐个计算4。

Yield

yield is a keyword that is used like return, except the function will return a generator.

yield是一个用于返回的关键字,除了函数将返回生成器。

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

Here it's a useless example, but it's handy when you know your function will return a huge set of values that you will only need to read once.

这是一个无用的例子,但是当你知道你的函数会返回一组你只需要读一次的值时,它就很方便了。

To master yield, you must understand that when you call the function, the code you have written in the function body does not run. The function only returns the generator object, this is a bit tricky :-)

要掌握yield,您必须理解,当您调用函数时,在函数体中编写的代码不会运行。函数只返回生成器对象,这有点棘手:-)

Then, your code will be run each time the for uses the generator.

然后,您的代码将在每次使用生成器时运行。

Now the hard part:

现在困难的部分:

The first time the for calls the generator object created from your function, it will run the code in your function from the beginning until it hits yield, then it'll return the first value of the loop. Then, each other call will run the loop you have written in the function one more time, and return the next value, until there is no value to return.

第一次调用从函数创建的生成器对象时,它将从开始运行到函数中的代码,直到它命中yield,然后它将返回循环的第一个值。然后,每个其他调用将运行您在函数中再次写入的循环,并返回下一个值,直到没有返回值为止。

The generator is considered empty once the function runs but does not hit yield anymore. It can be because the loop had come to an end, or because you do not satisfy an "if/else" anymore.

一旦函数运行,生成器就会被认为是空的,但它不会再命中yield。这可能是因为循环已经结束,或者因为您不再满足“if/else”。


Your code explained

Generator:

发电机:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

Caller:

打电话者:

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

This code contains several smart parts:

这段代码包含几个智能部分:

  • The loop iterates on a list but the list expands while the loop is being iterated :-) It's a concise way to go through all these nested data even if it's a bit dangerous since you can end up with an infinite loop. In this case, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) exhausts all the values of the generator, but while keeps creating new generator objects which will produce different values from the previous ones since it's not applied on the same node.

    循环在列表上迭代,但是在循环迭代时,列表会扩展:-)这是一种很简洁的方法,可以遍历所有这些嵌套的数据,即使它有点危险,因为您可以以一个无限循环结束。在这种情况下,candidates.extend(节点。_get_child_候选(distance, min_dist, max_dist))耗尽生成器的所有值,但同时继续创建新的生成器对象,该对象将产生与之前的不同的值,因为它不在同一个节点上应用。

  • The extend() method is a list object method that expects an iterable and adds its values to the list.

    extend()方法是一个列表对象方法,它期望迭代并将其值添加到列表中。

Usually we pass a list to it:

通常我们会给它一个列表:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

But in your code it gets a generator, which is good because:

但是在你的代码中它得到了一个生成器,这很好,因为:

  1. You don't need to read the values twice.
  2. 您不需要读取两次值。
  3. You may have a lot of children and you don't want them all stored in memory.
  4. 你可能有很多孩子,你不希望他们都存储在内存中。

And it works because Python does not care if the argument of a method is a list or not. Python expects iterables so it will work with strings, lists, tuples and generators! This is called duck typing and is one of the reason why Python is so cool. But this is another story, for another question...

这是因为Python并不关心方法的参数是否为列表。Python期待iterables,因此它将处理字符串、列表、元组和生成器!这就是所谓的duck typing,这也是Python之所以如此酷的原因之一。但这是另一个故事,另一个问题……

You can stop here, or read a little bit to see an advanced use of a generator:

你可以在这里停下来,或者读点东西,看看有什么先进的使用方法:

Controlling a generator exhaustion

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

Note: For Python3 useprint(corner_street_atm.__next__()) or print(next(corner_street_atm))

注意:对于Python3 useprint(_street_atm.__next__())或print(下一个(角_street_atm))

It can be useful for various things like controlling access to a resource.

它可以用于控制对资源的访问。

Itertools, your best friend

The itertools module contains special functions to manipulate iterables. Ever wish to duplicate a generator? Chain two generators? Group values in a nested list with a one liner? Map / Zip without creating another list?

itertools模块包含用于操作iterables的特殊函数。想要复制一个发电机吗?链两个发电机?嵌套列表中的组值与一行?Map / Zip而不创建另一个列表?

Then just import itertools.

然后就出现进口itertools。

An example? Let's see the possible orders of arrival for a 4 horse race:

一个例子吗?让我们来看看4匹马的到来的可能顺序:

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

Understanding the inner mechanisms of iteration

Iteration is a process implying iterables (implementing the __iter__() method) and iterators (implementing the __next__() method). Iterables are any objects you can get an iterator from. Iterators are objects that let you iterate on iterables.

迭代是一个意味着迭代的过程(实现__iter__()方法)和迭代器(实现__next__()方法)。Iterables是任何可以从一个迭代器获得的对象。迭代器是让您在iterables上迭代的对象。

More about it in this article about how for loops work.

本文将详细介绍如何循环工作。

#2


1564  

Shortcut to Grokking yield

When you see a function with yield statements, apply this easy trick to understand what will happen:

当你看到一个带有yield语句的函数时,应用这个简单的技巧来理解将要发生的事情:

  1. Insert a line result = [] at the start of the function.
  2. 在函数的开始处插入一行结果=[]。
  3. Replace each yield expr with result.append(expr).
  4. 用result.append(expr)替换每个yield expr。
  5. Insert a line return result at the bottom of the function.
  6. 在函数的底部插入一行返回结果。
  7. Yay - no more yield statements! Read and figure out code.
  8. 耶-不要再屈服了!阅读并计算代码。
  9. Compare function to original definition.
  10. 将函数与原始定义进行比较。

This trick may give you an idea of the logic behind the function, but what actually happens with yield is significantly different that what happens in the list based approach. In many cases the yield approach will be a lot more memory efficient and faster too. In other cases this trick will get you stuck in an infinite loop, even though the original function works just fine. Read on to learn more...

这个技巧可以让您了解函数背后的逻辑,但实际发生的情况与基于列表的方法有很大的不同。在许多情况下,收益率方法将会大大提高内存的效率和速度。在其他情况下,这个技巧会让你陷入无限循环,即使最初的函数很好。继续往下读,了解更多……

Don't confuse your Iterables, Iterators and Generators

First, the iterator protocol - when you write

首先,迭代器协议——当您编写时。

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

Python performs the following two steps:

Python执行以下两个步骤:

  1. Gets an iterator for mylist:

    获取mylist的迭代器:

    Call iter(mylist) -> this returns an object with a next() method (or __next__() in Python 3).

    调用iter(mylist) ->,它将以Python 3中的next()方法(或__next__())返回一个对象。

    [This is the step most people forget to tell you about]

    [这是大多数人忘记告诉你的步骤]

  2. Uses the iterator to loop over items:

    使用迭代器对项进行循环:

    Keep calling the next() method on the iterator returned from step 1. The return value from next() is assigned to x and the loop body is executed. If an exception StopIteration is raised from within next(), it means there are no more values in the iterator and the loop is exited.

    在第1步返回的迭代器上继续调用next()方法。下一个()的返回值被赋给x,然后执行循环体。如果在next()中引发异常停止迭代,则意味着迭代器中不再有值,循环退出。

The truth is Python performs the above two steps anytime it wants to loop over the contents of an object - so it could be a for loop, but it could also be code like otherlist.extend(mylist) (where otherlist is a Python list).

事实上,Python在任何时候都执行上述两个步骤,它希望对对象的内容进行循环——因此它可能是for循环,但它也可以是像otherlist.extend(mylist)那样的代码(其他列表是Python列表)。

Here mylist is an iterable because it implements the iterator protocol. In a user defined class, you can implement the __iter__() method to make instances of your class iterable. This method should return an iterator. An iterator is an object with a next() method. It is possible to implement both __iter__() and next() on the same class, and have __iter__() return self. This will work for simple cases, but not when you want two iterators looping over the same object at the same time.

这里mylist是一个可迭代的,因为它实现了迭代器协议。在用户定义的类中,您可以实现__iter__()方法,以使类的实例可以迭代。该方法应该返回一个迭代器。迭代器是带有next()方法的对象。在同一个类上实现__iter__()和next()是可能的,并且具有__iter__()返回self。这将适用于简单的情况,但不是当您希望两个迭代器同时遍历同一对象时。

So that's the iterator protocol, many objects implement this protocol:

这就是迭代器协议,很多对象实现了这个协议

  1. Built-in lists, dictionaries, tuples, sets, files.
  2. 内置列表,字典,元组,集,文件。
  3. User defined classes that implement __iter__().
  4. 用户定义的类实现__iter__()。
  5. Generators.
  6. 发电机。

Note that a for loop doesn't know what kind of object it's dealing with - it just follows the iterator protocol, and is happy to get item after item as it calls next(). Built-in lists return their items one by one, dictionaries return the keys one by one, files return the lines one by one, etc. And generators return... well that's where yield comes in:

注意,for循环不知道它要处理什么样的对象——它只遵循迭代器协议,并且很高兴在它调用next()之后获得item。内置的列表一个接一个地返回他们的项目,字典一个一个地返回键,文件一个接一个地返回,等等。这就是收益的来源:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Instead of yield statements, if you had three return statements in f123() only the first would get executed, and the function would exit. But f123() is no ordinary function. When f123() is called, it does not return any of the values in the yield statements! It returns a generator object. Also, the function does not really exit - it goes into a suspended state. When the for loop tries to loop over the generator object, the function resumes from its suspended state at the very next line after the yield it previously returned from, executes the next line of code, in this case a yield statement, and returns that as the next item. This happens until the function exits, at which point the generator raises StopIteration, and the loop exits.

如果在f123()中有三个返回语句,而不是yield语句,那么只有第一个语句会被执行,函数将退出。但是f123()不是普通的函数。当调用f123()时,它不会返回yield语句中的任何值!它返回一个生成器对象。而且,函数并没有真正退出——它进入了一个暂停状态。当for循环试图对生成器对象进行循环时,该函数从它的挂起状态恢复到下一行,在此之前,该函数会执行下一行代码,在本例中,执行下一行代码,并将其作为下一项返回。这种情况发生在函数退出之前,此时生成器将引发StopIteration,并且循环退出。

So the generator object is sort of like an adapter - at one end it exhibits the iterator protocol, by exposing __iter__() and next() methods to keep the for loop happy. At the other end however, it runs the function just enough to get the next value out of it, and puts it back in suspended mode.

因此,生成器对象有点类似于适配器——在一端,它展示了迭代器协议,通过暴露__iter__()和next()方法来保持for循环的快乐。然而,在另一端,它运行的功能刚好可以从它中获取下一个值,并将其返回到挂起模式中。

Why Use Generators?

Usually you can write code that doesn't use generators but implements the same logic. One option is to use the temporary list 'trick' I mentioned before. That will not work in all cases, for e.g. if you have infinite loops, or it may make inefficient use of memory when you have a really long list. The other approach is to implement a new iterable class SomethingIter that keeps state in instance members and performs the next logical step in it's next() (or __next__() in Python 3) method. Depending on the logic, the code inside the next() method may end up looking very complex and be prone to bugs. Here generators provide a clean and easy solution.

通常,您可以编写不使用生成器但实现相同逻辑的代码。一种选择是使用我之前提到的临时列表“技巧”。这在所有情况下都不起作用,例如,如果您有无限循环,或者当您有一个非常长的列表时,它可能会使内存使用效率低下。另一种方法是实现一个新的iterable类SomethingIter,它在实例成员中保持状态,并在它的下一个()(或__next__())方法中执行下一个逻辑步骤。根据逻辑,下一个()方法中的代码可能看起来非常复杂,并且容易出现错误。这里的生成器提供了一个干净和简单的解决方案。

#3


370  

Think of it this way:

这样想:

An iterator is just a fancy sounding term for an object that has a next() method. So a yield-ed function ends up being something like this:

迭代器对于具有next()方法的对象来说只是一个花哨的术语。所以一个屈服函数最终是这样的

Original version:

原始版本:

def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

This is basically what the python interpreter does with the above code:

这基本上就是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 more insight as to what's happening behind the scenes, the for loop can be rewritten to this:

为了更深入地了解幕后发生的事情,For循环可以改写为:

iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

Does that make more sense or just confuse you more? :)

这更有意义,还是让你更困惑?:)

EDIT: I should note that this IS an oversimplification for illustrative purposes. :)

编辑:我应该注意,这是为了说明的目的过于简化。:)

EDIT 2: Forgot to throw the StopIteration exception

编辑2:忘记抛出StopIteration异常。

#4


329  

The yield keyword is reduced to two simple facts:

yield关键字被简化为两个简单的事实:

  1. If the compiler detects the yield keyword anywhere inside a function, that function no longer returns via the return statement. Instead, it immediately returns a lazy "pending list" object called a generator
  2. 如果编译器在函数内的任何地方检测到yield关键字,则该函数不再通过返回语句返回。相反,它会立即返回一个名为生成器的惰性“挂起列表”对象。
  3. A generator is iterable. What is an iterable? It's anything like a list or set or range or dict-view, with a built-in protocol for visiting each element in a certain order.
  4. 发电机是iterable。什么是iterable ?它类似于列表或设置、范围或dict-view,它带有一个内置的协议,可以按一定的顺序访问每个元素。

In a nutshell: a generator is a lazy, incrementally-pending list, and yield statements allow you to use function notation to program the list values the generator should incrementally spit out.

简而言之:生成器是一个惰性的、递增的列表,而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]]

Example

Let's define a function makeRange that's just like Python's range. Calling makeRange(n) RETURNS A GENERATOR:

让我们定义一个函数makeRange,它就像Python的范围。调用makeRange(n)返回生成器:

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

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

To force the generator to immediately return its pending values, you can pass it into list() (just like you could any iterable):

为了强制生成器立即返回其挂起的值,您可以将其传递到list()(就像您可以迭代):

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

Comparing example to "just returning a list"

The above example can be thought of as merely creating a list which you append to and return:

上面的例子可以被认为仅仅是创建一个列表,并将其附加到并返回:

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

There is one major difference, though; see the last section.

但有一个主要区别;看到最后一节。


How you might use generators

An iterable is the last part of a list comprehension, and all generators are iterable, so they're often used like so:

迭代是列表理解的最后一部分,所有的生成器都是可迭代的,因此它们经常被使用:

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

To get a better feel for generators, you can play around with the itertools module (be sure to use chain.from_iterable rather than chain when warranted). For example, you might even use generators to implement infinitely-long lazy lists like itertools.count(). You could implement your own def enumerate(iterable): zip(count(), iterable), or alternatively do so with the yield keyword in a while-loop.

为了更好地了解生成器,您可以使用itertools模块(确保使用chain.from_iterable,而不是在必要时使用链)。例如,您甚至可以使用生成器来实现像itertools.count()这样的无限长的惰性列表。您可以实现自己的def enumerate(iterable): zip(count(), iterable),或者在while循环中使用yield关键字。

Please note: generators can actually be used for many more things, such as implementing coroutines or non-deterministic programming or other elegant things. However, the "lazy lists" viewpoint I present here is the most common use you will find.

请注意:生成器实际上可以用于更多的事情,例如实现coroutines或不确定性编程或其他优雅的东西。然而,我在这里展示的“懒惰列表”观点是您将会发现的最常见的用法。


Behind the scenes

This is how the "Python iteration protocol" works. That is, what is going on when you do list(makeRange(5)). This is what I describe earlier as a "lazy, incremental list".

这就是“Python迭代协议”的工作原理。也就是说,当你做列表的时候(makeRange(5))。这就是我之前所说的“懒惰的增量列表”。

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

The built-in function next() just calls the objects .next() function, which is a part of the "iteration protocol" and is found on all iterators. You can manually use the next() function (and other parts of the iteration protocol) to implement fancy things, usually at the expense of readability, so try to avoid doing that...

下面的内置函数()只调用对象。next()函数,它是“迭代协议”的一部分,在所有迭代器上都可以找到。您可以手动使用next()函数(以及迭代协议的其他部分)来实现一些花哨的东西,通常是以可读性为代价的,所以尽量避免这样做……


Minutiae

Normally, most people would not care about the following distinctions and probably want to stop reading here.

通常情况下,大多数人不关心下面的区别,可能想要停止阅读。

In Python-speak, an iterable is any object which "understands the concept of a for-loop" like a list [1,2,3], and an iterator is a specific instance of the requested for-loop like [1,2,3].__iter__(). A generator is exactly the same as any iterator, except for the way it was written (with function syntax).

在python语言中,iterable是任何“理解for循环”的对象,如列表[1,2,3],迭代器是请求for循环的特定实例,如[1,2,3].__iter__()。生成器与任何迭代器完全一样,只是它的编写方式(使用函数语法)除外。

When you request an iterator from a list, it creates a new iterator. However, when you request an iterator from an iterator (which you would rarely do), it just gives you a copy of itself.

当您从列表中请求迭代器时,它将创建一个新的迭代器。但是,当您从迭代器请求迭代器时(您很少会这样做),它只会给您一个自己的副本。

Thus, in the unlikely event that you are failing to do something like this...

因此,在不太可能的情况下,你不能做这样的事情……

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

... then remember that a generator is an iterator; that is, it is one-time-use. If you want to reuse it, you should call myRange(...) again. If you need to use the result twice, convert the result to a list and store it in a variable x = list(myRange(5)). Those who absolutely need to clone a generator (for example, who are doing terrifyingly hackish metaprogramming) can use itertools.tee if absolutely necessary, since the copyable iterator Python PEP standards proposal has been deferred.

…然后记住,生成器是迭代器;也就是说,它是一次性的。如果您想重用它,您应该再次调用myRange(…)。如果需要两次使用结果,请将结果转换为列表并将其存储在变量x = list(myRange(5))中。那些绝对需要克隆一个生成器的人(例如,正在做可怕的黑客元编程的人)可以使用itertools。tee如果绝对必要,因为可复制的迭代器Python PEP标准提案已经被推迟。

#5


226  

What does the yield keyword do in Python?

在Python中,yield关键字是干什么的?

Answer Outline/Summary

  • A function with yield, when called, returns a Generator.
  • 当调用一个函数时,它返回一个生成器。
  • Generators are iterators because they implement the iterator protocol, so you can iterate over them.
  • 生成器是迭代器,因为它们实现了迭代器协议,因此您可以迭代它们。
  • A generator can also be sent information, making it conceptually a coroutine.
  • 生成器也可以被发送信息,使它在概念上成为一个协同程序。
  • In Python 3, you can delegate from one generator to another in both directions with yield from.
  • 在Python 3中,您可以从一个生成器向两个方向的另一个生成器委托,并从中获得收益。
  • (Appendix critiques a couple of answers, including the top one, and discusses the use of return in a generator.)
  • (附录对几个答案进行了评论,包括最上面的一个,并讨论了在生成器中使用返回的问题。)

Generators:

yield is only legal inside of a function definition, and the inclusion of yield in a function definition makes it return a generator.

yield在函数定义中是合法的,在函数定义中包含yield使它返回生成器。

The idea for generators comes from other languages (see footnote 1) with varying implementations. In Python's Generators, the execution of the code is frozen at the point of the yield. When the generator is called (methods are discussed below) execution resumes and then freezes at the next yield.

生成生成器的想法来自于其他语言(参见脚注1),并有不同的实现。在Python的生成器中,代码的执行在yield的时候被冻结。当调用生成器时(下面将讨论方法),执行恢复,然后在下一次执行时冻结。

yield provides an easy way of implementing the iterator protocol, defined by the following two methods: __iter__ and next (Python 2) or __next__ (Python 3). Both of those methods make an object an iterator that you could type-check with the Iterator Abstract Base Class from the collections module.

yield提供了一种实现迭代器协议的简单方法,它由以下两种方法定义:__iter__和next (Python 2)或__next__ (Python 3)。

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

The generator type is a sub-type of iterator:

生成器类型是迭代器的子类型:

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

And if necessary, we can type-check like this:

如果有必要,我们可以这样检查:

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

A feature of an Iterator is that once exhausted, you can't reuse or reset it:

迭代器的一个特性是,一旦耗尽,就不能重用或重置它:

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

You'll have to make another if you want to use its functionality again (see footnote 2):

如果你想再次使用它的功能,你必须再做一个(参见脚注2):

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

One can yield data programmatically, for example:

一个可以以编程方式生成数据,例如:

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

The above simple generator is also equivalent to the below - as of Python 3.3 (and not available in Python 2), you can use yield from:

上面的简单生成器也等价于下面的Python 3.3(在Python 2中不可用),您可以使用:

def func(an_iterable):
    yield from an_iterable

However, yield from also allows for delegation to subgenerators, which will be explained in the following section on cooperative delegation with sub-coroutines.

但是,也允许向次级发电机派遣代表团,这将在下一节中解释与子协程的合作代表团。

Coroutines:

yield forms an expression that allows data to be sent into the generator (see footnote 3)

yield形成一个表达式,允许将数据发送到生成器(参见脚注3)

Here is an example, take note of the received variable, which will point to the data that is sent to the generator:

下面是一个示例,注意接收到的变量,它将指向发送给生成器的数据:

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)

First, we must queue up the generator with the builtin function, next. It will call the appropriate next or __next__ method, depending on the version of Python you are using:

首先,我们必须将生成器与builtin函数进行排队。它将调用适当的next或__next__方法,这取决于您使用的Python版本:

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

And now we can send data into the generator. (Sending None is the same as calling next.) :

现在我们可以把数据发送到生成器中。(发送没有一个和下一个是一样的。)

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

Cooperative Delegation to Sub-Coroutine with yield from

Now, recall that yield from is available in Python 3. This allows us to delegate coroutines to a subcoroutine:

现在,回想一下Python 3中可用的yield。这使得我们可以将coroutines委托给一个subcoroutine:

def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

And now we can delegate functionality to a sub-generator and it can be used by a generator just as above:

现在我们可以将功能委托给子生成器,它可以被一个生成器使用,就像上面一样:

>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

You can read more about the precise semantics of yield from in PEP 380.

您可以从PEP 380中了解更多关于yield的确切语义。

Other Methods: close and throw

The close method raises GeneratorExit at the point the function execution was frozen. This will also be called by __del__ so you can put any cleanup code where you handle the GeneratorExit:

在函数执行被冻结时,关闭方法将生成GeneratorExit。这也将被__del__调用,这样您就可以在处理GeneratorExit时使用任何清理代码:

>>> my_account.close()

You can also throw an exception which can be handled in the generator or propagated back to the user:

您还可以抛出一个异常,该异常可以在生成器中处理或向用户传播:

>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

Conclusion

I believe I have covered all aspects of the following question:

我相信我已经涵盖了以下问题的所有方面:

What does the yield keyword do in Python?

在Python中,yield关键字是干什么的?

It turns out that yield does a lot. I'm sure I could add even more thorough examples to this. If you want more or have some constructive criticism, let me know by commenting below.

结果是,产量有很大的影响,我相信我能在这上面再加上更彻底的例子。如果你想要更多或有一些建设性的批评,请通过下面的评论让我知道。


Appendix:

Critique of the Top/Accepted Answer**

  • It is confused on what makes an iterable, just using a list as an example. See my references above, but in summary: an iterable has an __iter__ method returning an iterator. An iterator provides a .next (Python 2 or .__next__ (Python 3) method, which is implicitly called by for loops until it raises StopIteration, and once it does, it will continue to do so.
  • 它混淆了什么是可迭代的,仅仅使用一个列表作为例子。参见上面的参考资料,但总结一下:iterable有一个__iter__方法返回一个迭代器。迭代器提供了一个.next (Python 2或.__next__ (Python 3)方法,它是由for循环隐式调用的,直到它产生StopIteration,一旦它完成,它将继续这样做。
  • It then uses a generator expression to describe what a generator is. Since a generator is simply a convenient way to create an iterator, it only confuses the matter, and we still have not yet gotten to the yield part.
  • 然后使用生成器表达式来描述生成器。由于生成器仅仅是创建迭代器的一种方便的方法,它只会混淆问题,而我们还没有到达yield部分。
  • In Controlling a generator exhaustion he calls the .next method, when instead he should use the builtin function, next. It would be an appropriate layer of indirection, because his code does not work in Python 3.
  • 在控制发电机耗竭时,他调用。next方法,当他应该使用builtin函数时,next。这将是一个适当的间接层,因为他的代码在Python 3中不起作用。
  • Itertools? This was not relevant to what yield does at all.
  • Itertools吗?这与收益率的作用无关。
  • No discussion of the methods that yield provides along with the new functionality yield from in Python 3. The top/accepted answer is a very incomplete answer.
  • 不讨论yield提供的方法以及Python 3中提供的新功能。答案是一个非常不完整的答案。

Critique of answer suggesting yield in a generator expression or comprehension.

The grammar currently allows any expression in a list comprehension.

语法目前允许列表理解中的任何表达式。

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

Since yield is an expression, it has been touted by some as interesting to use it in comprehensions or generator expression - in spite of citing no particularly good use-case.

由于yield是一个表达式,它被一些人吹捧为在理解或生成器表达式中使用它,尽管引用的不是特别好的用例。

The CPython core developers are discussing deprecating its allowance. Here's a relevant post from the mailing list:

CPython的核心开发人员正在讨论减少其津贴。以下是邮件列表中的相关文章:

On 30 January 2017 at 19:05, Brett Cannon wrote:

2017年1月30日19:05,Brett Cannon写道:

On Sun, 29 Jan 2017 at 16:39 Craig Rodrigues wrote:

2017年1月29日16时39分,克雷格·罗德里格斯(Craig Rodrigues)在《太阳报》上写道:

I'm OK with either approach. Leaving things the way they are in Python 3 is no good, IMHO.

这两种方法我都可以接受。在Python 3中保留它们的方式并不好,IMHO。

My vote is it be a SyntaxError since you're not getting what you expect from the syntax.

我的投票是一个SyntaxError,因为你没有得到你期望的语法。

I'd agree that's a sensible place for us to end up, as any code relying on the current behaviour is really too clever to be maintainable.

我同意这对我们来说是一个明智的选择,因为任何依赖于当前行为的代码都非常聪明,难以维护。

In terms of getting there, we'll likely want:

在到达目的地方面,我们可能会想:

  • SyntaxWarning or DeprecationWarning in 3.7
  • 3.7的SyntaxWarning或DeprecationWarning。
  • Py3k warning in 2.7.x
  • 在2.7.x Py3k警告
  • SyntaxError in 3.8
  • SyntaxError在3.8

Cheers, Nick.

干杯,尼克。

-- Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia

——澳大利亚布里斯班布里斯班的Nick Coghlan | ncoghlan。

Further, there is an outstanding issue (10544) which seems to be pointing in the direction of this never being a good idea (PyPy, a Python implementation written in Python, is already raising syntax warnings.)

此外,还有一个悬而未决的问题(10544),它似乎指向了这个问题的方向(PyPy, Python的Python实现,它已经在提高语法警告了)。

Bottom line, until the developers of CPython tell us otherwise: Don't put yield in a generator expression or comprehension.

总之,直到CPython的开发人员告诉我们:不要在生成器的表达式或理解中使用yield。

The return statement in a generator

In Python 2:

在Python中2:

In a generator function, the return statement is not allowed to include an expression_list. In that context, a bare return indicates that the generator is done and will cause StopIteration to be raised.

在生成器函数中,返回语句不允许包含expression_list。在这种情况下,一个裸返回表明生成器已经完成,并将导致停止迭代。

An expression_list is basically any number of expressions separated by commas - essentially, in Python 2, you can stop the generator with return, but you can't return a value.

expression_list基本上是由逗号分隔的任意数量的表达式——实际上,在Python 2中,可以用return停止生成器,但是不能返回值。

In Python 3:

在Python 3:

In a generator function, the return statement indicates that the generator is done and will cause StopIteration to be raised. The returned value (if any) is used as an argument to construct StopIteration and becomes the StopIteration.value attribute.

在生成器函数中,返回语句表明生成器已完成,并将导致停止迭代。返回值(如果有的话)用作构造StopIteration的参数,并成为StopIteration。价值属性。

Footnotes

  1. The languages CLU, Sather, and Icon were referenced in the proposal to introduce the concept of generators to Python. The general idea is that a function can maintain internal state and yield intermediate data points on demand by the user. This promised to be superior in performance to other approaches, including Python threading, which isn't even available on some systems.

    在向Python引入生成器概念的建议中,引用了语言、语言、语言和图标。一般的观点是,一个函数可以保持内部状态,并根据用户的需求生成中间数据点。这保证在性能上优于其他方法,包括Python线程,这在某些系统上甚至是不可用的。

  2. This means, for example, that xrange objects (range in Python 3) aren't Iterators, even though they are iterable, because they can be reused. Like lists, their __iter__ methods return iterator objects.

    例如,这意味着xrange对象(Python 3中的范围)不是迭代器,即使它们是可迭代的,因为它们可以被重用。与列表一样,它们的__iter__方法返回迭代器对象。

  3. yield was originally introduced as a statement, meaning that it could only appear at the beginning of a line in a code block. Now yield creates a yield expression. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt This change was proposed to allow a user to send data into the generator just as one might receive it. To send data, one must be able to assign it to something, and for that, a statement just won't work.

    yield最初是作为一个语句引入的,这意味着它只能出现在代码块中的一行的开头。现在,收益率创造了一个收益表达式。这一更改被建议允许用户将数据发送到生成器中,就像人们可能接收到的那样。要发送数据,一个人必须能够将其分配给某样东西,为此,一个语句将不起作用。

#6


213  

yield is just like return - it returns whatever you tell it to. The only difference is that the next time you call the function, execution starts from the last call to the yield statement.

收益就像回报一样——它会返回你告诉它的任何东西。惟一的区别是,下一次调用该函数时,执行从上次调用yield语句开始。

In the case of your code, the function get_child_candidates is acting like an iterator so that when you extend your list, it adds one element at a time to the new list.

在代码的情况下,函数get_child_候选者表现得像一个迭代器,这样当您扩展列表时,它会在新列表的时候添加一个元素。

list.extend calls an iterator until it's exhausted. In the case of the code sample you posted, it would be much clearer to just return a tuple and append that to the list.

列表。扩展调用迭代器,直到它耗尽为止。在您发布的代码示例的情况下,只需返回一个tuple并将其添加到列表中会更加清楚。

#7


174  

There's one extra thing to mention: a function that yields doesn't actually have to terminate. I've written code like this:

还有一件事需要提一下:一个函数,它的收益率实际上不需要终止。我写过这样的代码:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

Then I can use it in other code like this:

然后我可以在其他代码中使用它:

for f in fib():
    if some_condition: break
    coolfuncs(f);

It really helps simplify some problems, and makes some things easier to work with.

它确实有助于简化一些问题,并使一些事情更容易处理。

#8


144  

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

对于那些喜欢使用最小工作示例的人,请在交互式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

#9


125  

Yield gives you a generator.

产量给你一个发电机。

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

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

如您所见,在第一个case中,foo将整个列表同时保存在内存中。对于一个包含5个元素的列表来说,这没什么大不了的,但是如果你想要一个500万的列表呢?这不仅是一个巨大的内存消耗者,而且在调用该函数时还需要花费大量的时间。在第二个例子中,bar只是给你一个生成器。生成器是可迭代的——这意味着您可以在for循环中使用它,但是每个值只能被访问一次。所有的值也不会同时存储在内存中;生成器对象“记得”上次调用它时在循环中的位置——这种方式,如果您使用的是iterable(比方说)数到500亿,那么您不必同时数到500亿,并存储500亿个数字。同样,这是一个精心设计的例子,如果你真的想数到500亿的话,你可能会使用itertools。:)

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

这是生成器最简单的用例。正如您所说的,它可以用来编写高效的排列,使用yield来将事情推到调用堆栈中,而不是使用某种堆栈变量。生成器还可以用于特殊的树遍历,以及其他所有方式。

#10


117  

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

它返回一个发电机。我对Python不是特别熟悉,但我相信如果您熟悉Python,它与c#的迭代器块是一样的。

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

有一篇IBM的文章可以很好地解释它(对于Python),就我所见。

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

关键的思想是编译器/解释器/无论什么方法,只要调用者关心,他们就可以继续调用next(),并且它将保持返回值——就好像生成器方法被暂停了一样。很明显,你不能“暂停”一个方法,所以编译器会建立一个状态机来让你记住你当前的位置,以及局部变量是什么样子。这比自己编写迭代器要容易得多。

#11


115  

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

有一种回答,我觉得还没有给出,在许多描述如何使用发电机的伟大答案中。这是PL理论的答案:

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

python中的yield语句返回一个生成器。python中的生成器是一个返回延续的函数(特别是一种类型的coroutine,但是continuations表示更通用的机制来理解正在发生的事情)。

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

在编程语言理论中,延续是一种更基本的计算方法,但它们并不经常被使用,因为它们非常难于推理,而且很难实现。但是,延续的概念很简单:它是尚未完成的计算的状态。在这个状态中,保存了变量的当前值和尚未执行的操作,等等。然后在程序的某个时候,可以调用continuation,这样程序的变量就会被重置为那个状态,而保存的操作就会被执行。

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

在这种更通用的形式中,延续可以通过两种方式实现。在调用/cc方式中,程序的堆栈实际上是保存的,然后在调用continuation时,将恢复堆栈。

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

在延续传递样式(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 )

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

在这个(非常简单)的例子中,程序员将实际编写文件的操作保存为continuation(这可能是一个非常复杂的操作,有许多细节需要写出来),然后通过这个延续(i。e,作为一个一流的闭包)给另一个操作员,它做了更多的处理,然后在必要时调用它。(我在实际的GUI编程中经常使用这种设计模式,因为它节省了代码行,或者更重要的是,在GUI事件触发后管理控制流)

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

这篇文章的其余部分将不会失去一般性,将延续作为CPS,因为它更容易理解和阅读。


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

现在让我们讨论一下python中的生成器。生成器是延续的特定子类型。然而,continuations通常可以保存计算的状态(也就是)。程序的调用栈),生成器只能通过迭代器来保存迭代的状态。尽管,这一定义对于某些用例生成器有轻微的误导。例如:

def f():
  while True:
    yield 4

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

这显然是一个合理的迭代,它的行为是很好的定义的——每次生成器遍历它时,它返回4(并且永远这样做)。但是,当迭代器的思想(即,迭代器)时,它可能不是最典型的迭代类型。,for x in collection: do_something(x))。这个例子说明了生成器的力量:如果任何东西都是迭代器,那么生成器可以保存它的迭代状态。

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

重申:Continuations可以保存程序堆栈的状态,而生成器可以保存迭代的状态。这意味着延续比生成器更强大,但也更容易生成生成器。它们对于语言设计人员来说更容易实现,而且它们对程序员来说更容易使用(如果您有一些时间可以使用,请尝试阅读并理解关于continuations和call/cc的这一页)。

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

但是您可以轻松地实现(和概念化)生成器作为一个简单的、特定的延续传递样式的例子:

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

无论何时调用yield,它都会告诉函数返回一个延续。当函数再次被调用时,它从它停止的地方开始。生成器的下一种方法基本上是:

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

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

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

这里的yield关键字是真正的生成器函数的语法糖,基本上是这样的:

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

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

请记住,这只是伪代码,而python中生成器的实际实现更为复杂。但是作为一种理解正在发生的事情的练习,尝试使用延续传递样式来实现生成器对象,而不使用yield关键字。

#12


109  

TL;DR

博士TL;

When you find yourself building a list from scratch...

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

...yield each piece instead

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

This was my first "aha" moment with yield.

这是我第一次收获“啊哈”时刻。


yield is a sugary way to say

屈服是一种甜蜜的说法。

build a series of stuff

建立一系列的东西。

Same behavior:

同样的行为:

>>> for square in squares_list(4):
...     print(square)
...
0
1
4
9
>>> for square in squares_the_yield_way(4):
...     print(square)
...
0
1
4
9

Different behavior:

不同的行为:

Yield is single-pass: you can only iterate through once. When a function has a yield in it we call it a generator function. And an iterator is what it returns. That's revealing. We lose the convenience of a container, but gain the power of an arbitrarily long series.

收益率是单通道的:您只能遍历一次。当一个函数有一个收益率时,我们称它为生成器函数。迭代器就是它返回的内容。这是揭示。我们失去了一个容器的便利,但却获得了一个任意长的系列的力量。

Yield is lazy, it puts off computation. A function with a yield in it doesn't actually execute at all when you call it. The iterator object it returns uses magic to maintain the function's internal context. Each time you call next() on the iterator (this happens in a for-loop) execution inches forward to the next yield. (return raises StopIteration and ends the series.)

收益是懒惰的,它会推迟计算。一个具有收益率的函数在调用它时实际上根本不会执行。它返回的迭代器对象使用魔法来维护函数的内部上下文。每次在迭代器上调用next(在for循环中发生)时,就会向前移动到下一个yield。(return增加了StopIteration并结束了这个系列。)

Yield is versatile. It can do infinite loops:

收益率是多才多艺的。它可以做无限循环:

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

If you need multiple passes and the series isn't too long, just call list() on it:

如果您需要多个传递,并且这个系列不太长,只需调用列表():

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

Brilliant choice of the word yield because both meanings apply:

“收益”的明智选择,因为这两种含义都适用:

yield — produce or provide (as in agriculture)

产量-生产或供应(如农业)

...provide the next data in the series.

…提供该系列中的下一个数据。

yield — give way or relinquish (as in political power)

屈服-让步或放弃(如在*中)

...relinquish CPU execution until the iterator advances.

…在迭代器前进之前放弃CPU执行。

#13


99  

An example in plain language. I will provide a correspondence between high-level human concepts to low-level python concepts.

简单语言的一个例子。我将提供高级的人类概念与低级python概念之间的对应关系。

I want to operate on a sequence of numbers, but I don't want to bother my self with the creation of that sequence, I want only to focus on the operation I want to do. So, I do the following:

我想要操作一个数字序列,但是我不想用那个序列的创建来打扰我自己,我只想专注于我想要做的操作。所以,我做如下工作:

  • I call you and tell you that I want a sequence of numbers which is produced in a specific way, and I let you know what the algorithm is.
    This step corresponds to defining the generator function, i.e. the function containing a yield.
  • 我给你打电话,告诉你我想要一个以特定的方式产生的数字序列,我让你知道这个算法是什么。此步骤对应于定义生成器函数,即包含yield的函数。
  • Sometime later, I tell you, "ok, get ready to tell me the sequence of numbers".
    This step corresponds to calling the generator function which returns a generator object. Note that you don't tell me any numbers yet, you just grab your paper and pencil.
  • 后来,我告诉你,“好吧,准备告诉我数字的顺序”。此步骤对应于调用返回生成器对象的生成器函数。注意,你还没有告诉我任何数字,你只要拿起纸和笔就行了。
  • I ask you, "tell me the next number", and you tell me the first number; after that, you wait for me to ask you for the next number. It's your job to remember where you were, what numbers you have already said, what is the next number. I don't care about the details.
    This step corresponds to calling .next() on the generator object.
  • 我问你,“告诉我下一个数字”,你告诉我第一个数字;在那之后,你等着我问你下一个号码。你的工作就是记住你在哪里,你已经说过什么数字,下一个数字是什么。我不在乎细节。此步骤对应于在生成器对象上调用.next()。
  • … repeat previous step, until…
  • 重复前面的步骤,直到…
  • eventually, you might come to an end. You don't tell me a number, you just shout, "hold your horses! I'm done! No more numbers!"
    This step corresponds to the generator object ending its job, and raising a StopIteration exception The generator function does not need to raise the exception, it's raised automatically when the function ends or issues a return.
  • 最终,你可能会走到尽头。你不告诉我一个数字,你就喊:“住手!”我完成了!没有更多的数字!”此步骤对应于结束其作业的生成器对象,并引发一个StopIteration异常,生成器函数不需要引发异常,当函数结束或返回时,它会自动被提升。

This is what a generator does (a function that contains a yield); it starts executing, pauses whenever it does a yield, and when asked for a .next() value it continues from the point it was last. It fits perfectly by design with the iterator protocol of python, which describes how to sequentially request for values.

这是一个生成器所做的(一个包含yield的函数);它开始执行,每当它执行一个yield时暂停,当被请求一个。next()值时,它从最后一个点继续。它完全符合python的迭代器协议的设计,它描述了如何顺序地请求值。

The most famous user of the iterator protocol is the for command in python. So, whenever you do a:

迭代器协议最著名的用户是python中的for命令。所以,当你做a:

for item in sequence:

it doesn't matter if sequence is a list, a string, a dictionary or a generator object like described above; the result is the same: you read items off a sequence one by one.

无论序列是列表、字符串、字典还是像上面描述的生成器对象,都不重要;结果是一样的:您逐个读取一个序列中的项。

Note that defining a function which contains a yield keyword is not the only way to create a generator; it's just the easiest way to create one.

注意,定义包含yield关键字的函数并不是创建生成器的唯一方法;这是最简单的方法。

For more accurate information, read about iterator types, the yield statement and generators in the Python documentation.

要获得更准确的信息,请阅读Python文档中的迭代器类型、yield语句和生成器。

#14


92  

While a lot of answers show why you'd use a yield to create a generator, there are more uses for yield. It's quite easy to make a coroutine, which enables the passing of information between two blocks of code. I won't repeat any of the fine examples that have already been given about using yield to create a generator.

虽然有很多答案说明了为什么要使用yield来创建生成器,但是对于yield有更多的用途。创建一个coroutine是相当容易的,它支持在两个代码块之间传递信息。我不会重复已经给出的关于使用yield来创建生成器的优秀示例。

To help understand what a yield does in the following code, you can use your finger to trace the cycle through any code that has a yield. Every time your finger hits the yield, you have to wait for a next or a send to be entered. When a next is called, you trace through the code until you hit the yield… the code on the right of the yield is evaluated and returned to the caller… then you wait. When next is called again, you perform another loop through the code. However, you'll note that in a coroutine, yield can also be used with a send… which will send a value from the caller into the yielding function. If a send is given, then yield receives the value sent, and spits it out the left hand side… then the trace through the code progresses until you hit the yield again (returning the value at the end, as if next was called).

为了帮助理解在以下代码中收益率的作用,您可以使用您的手指在任何有收益的代码中跟踪循环。每次当你的手指按下产量时,你必须等待下一个或一个发送进入。当调用next时,您可以在代码中跟踪代码,直到到达yield……在yield的右侧的代码被评估并返回给调用者,然后等待。当再次调用next时,您将通过代码执行另一个循环。但是,您会注意到,在coroutine中,yield也可以用于发送,这将从调用者发送一个值到生成函数中。如果发送了一个send,那么yield就会接收到发送的值,然后将它从左边发送出去……然后通过代码的跟踪直到您再次命中yield(最后返回值,就像下一个被调用的那样)。

For example:

例如:

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

#15


80  

There is another yield use and meaning (since python 3.3):

还有另一个yield用法和含义(因为python 3.3):

yield from <expr>

http://www.python.org/dev/peps/pep-0380/

http://www.python.org/dev/peps/pep-0380/

A syntax is proposed for a generator to delegate part of its operations to another generator. This allows a section of code containing 'yield' to be factored out and placed in another generator. Additionally, the subgenerator is allowed to return with a value, and the value is made available to the delegating generator.

提出了一种语法,用于生成器将其部分操作委托给另一个生成器。这允许将包含“yield”的代码段分解并放置到另一个生成器中。此外,还允许子生成器以一个值返回,并将值提供给委托生成器。

The new syntax also opens up some opportunities for optimisation when one generator re-yields values produced by another.

新语法也为优化提供了一些机会,当一个生成器重新生成另一个生成器生成的值时。

moreover this will introduce (since python 3.5):

此外,这将引入(自python 3.5):

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

to avoid coroutines confused with regular generator (today yield is used in both).

为了避免与普通的发电机产生混淆(今天的产量在两者中都使用)。

#16


74  

I was going to post "read page 19 of Beazley's 'Python: Essential Reference' for a quick description of generators", but so many others have posted good descriptions already.

我将发表“Beazley’s Python的第19页:对生成器的快速描述的基本参考”,但是其他许多人已经发布了很好的描述。

Also, note that yield can be used in coroutines as the dual of their use in generator functions. Although it isn't the same use as your code snippet, (yield) can be used as an expression in a function. When a caller sends a value to the method using the send() method, then the coroutine will execute until the next (yield) statement is encountered.

此外,请注意,产量可以作为它们在发电机功能中使用的双重用途,在coroutines中使用。尽管它与您的代码片段不一样,但(yield)可以作为函数中的表达式。当调用者使用send()方法向方法发送一个值时,将执行coroutine,直到遇到下一个(yield)语句。

Generators and coroutines are a cool way to set up data-flow type applications. I thought it would be worthwhile knowing about the other use of the yield statement in functions.

生成器和coroutines是设置数据流类型应用程序的一种很酷的方法。我想知道在函数中使用yield语句的其他用法是值得的。

#17


71  

Here are some Python examples of how to actually implement generators as if Python did not provide syntactic sugar for them:

下面是一些关于如何实际实现生成器的Python示例,就像Python没有为它们提供语法糖一样:

As a Python generator:

作为一个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))

Using lexical closures instead of generators

使用词法闭包代替生成器。

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)

Using object closures instead of generators (because ClosuresAndObjectsAreEquivalent)

使用对象闭包代替生成器(因为closuresandobjectsare等效)

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)

#18


66  

From a programming viewpoint, the iterators are implemented as thunks

从编程的观点来看,迭代器是作为thunks实现的。

http://en.wikipedia.org/wiki/Thunk_(functional_programming)

http://en.wikipedia.org/wiki/Thunk_(functional_programming)

To implement iterators/generators/thread pools for concurrent execution/etc as thunks (also called anonymous functions), one uses messages sent to a closure object, which has a dispatcher, and the dispatcher answers to "messages".

为了实现并行执行/etc(也称为匿名函数)的迭代器/生成器/线程池,一个使用发送到闭包对象的消息,其中有一个dispatcher,以及dispatcher对“消息”的应答。

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

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

"next" is a message sent to a closure, created by "iter" call.

“下一个”是发送到闭包的消息,由“iter”调用创建。

There are lots of ways to implement this computation. I used mutation but it is easy to do it without mutation, by returning the current value and the next yielder.

有很多方法可以实现这个计算。我使用了突变,但很容易做到,没有突变,通过返回当前值和下一个屈服点。

Here is a demonstration which uses the structure of R6RS but the semantics is absolutely identical as in python, it's the same model of computation, only a change in syntax is required to rewrite it in python.

这里有一个使用R6RS结构的演示,但是语义与python完全相同,它是相同的计算模型,只需修改语法就可以用python重写它。

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
-> 

#19


60  

Here is a simple example:

这里有一个简单的例子:

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)   

output :

输出:

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

I am not a Python developer, but it looks to me yield holds the position of program flow and the next loop start from "yield" position. It seems like it is waiting at that position, and just before that, returning a value outside, and next time continues to work.

我不是一个Python开发人员,但它看起来对我来说是程序流的位置,下一个循环从“yield”位置开始。它似乎在等待那个位置,在此之前,返回一个值,然后下一次继续工作。

Seems to me an interesting and nice ability :D

在我看来,这是一个有趣而又美好的能力:D。

#20


53  

Here is a mental image of what yield does.

这是一个关于收益率的心理图像。

I like to think of a thread as having a stack (even when it's not implemented that way).

我喜欢把线程看作是有堆栈的(即使它不是那样实现的)。

When a normal function is called, it puts its local variables on the stack, does some computation, then clears the stack and returns. The values of its local variables are never seen again.

当调用一个正常函数时,它将本地变量放在堆栈上,进行一些计算,然后清除堆栈并返回。它的局部变量的值再也不会出现。

With a yield function, when its code begins to run (i.e. after the function is called, returning a generator object, whose next() method is then invoked), it similarly puts its local variables onto the stack and computes for a while. But then, when it hits the yield statement, before clearing its part of the stack and returning, it takes a snapshot of its local variables and stores them in the generator object. It also writes down the place where it's currently up to in its code (i.e. the particular yield statement).

使用yield函数,当代码开始运行时(即调用函数后,返回一个生成器对象,然后调用其下一个()方法),它同样将其本地变量放到堆栈上并计算一段时间。但是,当它到达yield语句时,在清理堆栈的一部分并返回之前,它会对本地变量进行快照,并将它们存储在生成器对象中。它还会写下当前代码(即特定的yield语句)的位置。

So it's a kind of a frozen function that the generator is hanging onto.

这是一个冻结的函数。

When next() is called subsequently, it retrieves the function's belongings onto the stack and re-animates it. The function continues to compute from where it left off, oblivious to the fact that it had just spent an eternity in cold storage.

当next()随后被调用时,它将检索该函数的所有属性到堆栈上并重新激活它。这个函数继续从它停止的地方进行计算,忘记了它刚刚在冷库中度过了一个永恒。

Compare the following examples:

比较下面的例子:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

When we call the second function, it behaves very differently to the first. The yield statement might be unreachable, but if it's present anywhere, it changes the nature of what we're dealing with.

当我们调用第二个函数时,它和第一个函数有很大的不同。yield语句可能是不可访问的,但是如果它存在于任何地方,它会改变我们处理的内容的性质。

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

Calling yielderFunction() doesn't run its code, but makes a generator out of the code. (Maybe it's a good idea to name such things with the yielder prefix for readability.)

调用producderfunction()并不是运行它的代码,而是从代码中生成一个生成器。(也许用让步的前缀来命名这些东西是一个好主意。)

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

The gi_code and gi_frame fields are where the frozen state is stored. Exploring them with dir(..), we can confirm that our mental model above is credible.

gi_code和gi_frame字段是存储冻结状态的地方。用dir(..)来探索它们,我们可以确认上面的心智模型是可信的。

#21


40  

Like every answer suggests, yield is used for creating a sequence generator. It's used for generating some sequence dynamically. Eg. While reading a file line by line on a network, you can use the yield function as follows:

就像所有的答案一样,yield用于创建序列生成器。它用于动态生成一些序列。如。在网络上逐行读取文件时,可以使用yield函数如下:

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

You can use it in your code as follows :

你可以在你的代码中使用它:

for line in getNextLines():
    doSomeThing(line)

Execution Control Transfer gotcha

执行控制转移问题

The execution control will be transferred from getNextLines() to the for loop when yield is executed. Thus, every time getNextLines() is invoked, execution begins from the point where it was paused last time.

执行控制将从getNextLines()转移到执行yield时的for循环。因此,每次调用getNextLines()时,执行从上次暂停的点开始。

Thus in short, a function with the following code

因此,简而言之,一个具有以下代码的函数。

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

will print

将打印

"first time"
"second time"
"third time"
"Now some useful value 12"

I hope this helps you.

我希望这能帮到你。

#22


35  

Yield is an Object

收益率是一个对象

A return in a function will return a single value.

函数的返回值将返回一个值。

If you want function to return huge set of values use yield.

如果您希望函数返回大量的值,则使用yield。

More importantly, yield is a barrier

更重要的是,收益率是一个障碍。

like Barrier in Cuda Language, it will not transfer control until it gets completed.

就像Cuda语言中的障碍一样,它在完成之前不会转移控制。

i.e

It will run the code in your function from the beginning until it hits yield. Then, it’ll return the first value of the loop. Then, every other call will run the loop you have written in the function one more time, returning the next value until there is no value to return.

它将从一开始就运行函数中的代码,直到它命中yield。然后,它将返回循环的第一个值。然后,所有其他调用将运行您在函数中再次写入的循环,返回下一个值,直到没有返回值为止。

#23


33  

yield is like a return element for a function. The difference is, that the yield element turns a function into a generator. A generator behaves just like a function until something is 'yielded'. The generator stops until it is next called, and continues from exactly the same point as it started. You can get a sequence of all the 'yielded' values in one, by calling list(generator()).

yield是函数的返回元素。不同之处在于,yield元素将一个函数变成了一个生成器。生成器的行为就像一个函数,直到某个东西被“屈服”为止。生成器将停止,直到它被调用,并且从它开始的完全相同的点继续。通过调用list(生成器()),可以在其中一个中获得所有“生成”值的序列。

#24


33  

(My below answer only speaks from the perspective of using Python generator, not the underlying implementation of generator mechanism, which involves some tricks of stack and heap manipulation.)

(下面的回答只从使用Python生成器的角度进行,而不是生成器机制的底层实现,它涉及一些堆栈和堆操作的技巧。)

When yield is used instead of a return in a python function, that function is turned into something special called generator function. That function will return an object of generator type. The yield keyword is a flag to notify the python compiler to treat such function specially. Normal functions will terminate once some value is returned from it. But with the help of the compiler, the generator function can be thought of as resumable. That is, the execution context will be restored and the execution will continue from last run. Until you explicitly call return, which will raise a StopIteration exception (which is also part of the iterator protocol), or reach the end of the function. I found a lot of references about generator but this one from the functional programming perspective is the most digestable.

当使用yield而不是python函数的返回时,该函数被转换为一个特殊的称为生成器函数。该函数将返回生成器类型的对象。yield关键字是通知python编译器专门处理该函数的标志。当某个值从它返回时,正常函数将终止。但是在编译器的帮助下,可以认为生成器函数是可恢复的。也就是说,执行上下文将被恢复,执行将从上次运行继续。在您显式地调用return之前,它将引发一个StopIteration异常(它也是迭代器协议的一部分),或者到达函数的末尾。我发现了很多关于生成器的引用,但是从函数式编程的角度来看,这是最易于消化的。

(Now I want to talk about the rationale behind generator, and the iterator based on my own understanding. I hope this can help you grasp the essential motivation of iterator and generator. Such concept shows up in other languages as well such as C#.)

现在我想谈谈生成器背后的基本原理,以及基于我自己的理解的迭代器。我希望这能帮助您掌握迭代器和生成器的基本动机。这样的概念在其他语言中也会出现,比如c#。

As I understand, when we want to process a bunch of data, we usually first store the data somewhere and then process it one by one. But this intuitive approach is problematic. If the data volume is huge, it's expensive to store them as a whole beforehand. So instead of storing the data itself directly, why not store some kind of metadata indirectly, i.e. the logic how the data is computed.

正如我所理解的,当我们想要处理一堆数据时,通常先将数据存储在某个地方,然后逐个处理。但是这种直观的方法是有问题的。如果数据量是巨大的,那么预先存储它们是很昂贵的。因此,与其直接存储数据本身,为什么不直接存储某种元数据,即如何计算数据的逻辑。

There are 2 approaches to wrap such metadata.

有两种方法来包装这些元数据。

  1. The OO approach, we wrap the metadata as a class. This is the so-called iterator who implements the iterator protocol (i.e. the __next__(), and __iter__() methods). This is also the commonly seen iterator design pattern.
  2. 面向对象方法,我们将元数据打包为一个类。这就是所谓的迭代器,它实现了迭代器协议(即__next__()和__iter__()方法)。这也是常见的迭代器设计模式。
  3. The functional approach, we wrap the metadata as a function. This is the so-called generator function. But under the hood, the returned generator object still IS-A iterator because it also implements the iterator protocol.
  4. 函数方法,我们将元数据包装成函数。这就是所谓的发电机功能。但是在引擎盖下,返回的生成器对象仍然是- a迭代器,因为它还实现了迭代器协议。

Either way, an iterator is created, i.e. some object that can give you the data you want. The OO approach may be a bit complex. Anyway, which one to use is up to you.

无论哪种方式,都创建了一个迭代器,即一些可以提供所需数据的对象。OO方法可能有点复杂。不管怎样,用哪一个取决于你。

#25


30  

The yield keyword simply collects returning results. Think of yield like return +=

yield关键字只是收集返回的结果。考虑收益率,比如return +=。

#26


29  

All great answers whereas a bit difficult for newbies.

所有伟大的答案,而对新手来说有点困难。

I assume you have learned return statement.
As an analogy, return and yield are twins.
return means 'Return and Stop' whereas 'yield` means 'Return but Continue'

我想你们已经学过了return语句。作为一个类比,回报和收益是双胞胎。return是" return and Stop "而" yield "的意思是" return but Continue "

  1. Try to get a num_list with return.
  2. 尝试获得一个返回的num_list。
def num_list(n):
    for i in range(n):
        return i

Run it:

运行该程序:

In [5]: num_list(3)
Out[5]: 0

See, you get only a single number instead of a list of them,. return never allow you happy to prevail. It implemented once and quit.

看,你只得到一个数字而不是一个列表。回报永远不会让你快乐。它实现了一次并退出了。

  1. There comes yield
  2. 有产量

Replace return with 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]

Now, you win to get all the numbers.
Comparing to return which runs once and stops, yield runs times you planed.
You can interpret return as return one of them,
yield as return all of them. This is called iterable.

现在,你赢了所有的数字。与运行一次和停止的返回相比,您计划的收益运行时间。你可以解释return作为回报其中之一,收益率作为回报。这就是所谓的iterable。

  1. One more step we can rewrite yield statement with return
  2. 还有一步,我们可以用return重写yield语句。
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]

It's the core about yield.

这是产量的核心。

The difference between a list return outputs and the object yield output is:
You can get [0, 1, 2] from a list object always whereas can only retrieve them from 'the object yield output' once.
So, it has a new name generator object as displayed in Out[11]: <generator object num_list at 0x10327c990>.

列表返回输出和对象输出结果之间的区别是:您可以从列表对象中获取[0,1,2],而只能从“对象产量输出”中获得一次。因此,它有一个新的名称生成器对象,显示在Out[11]:

In conclusion as a metaphor to grok it,

总之,作为一个隐喻,

return and yield are twins,
list and generator are twins.

return和yield是双胞胎,list和generator是双胞胎。

#27


27  

Here's a simple yield based approach, to compute the fibonacci series, explained:

这里有一个简单的基于收益的方法,来计算斐波那契数列,解释如下:

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

When you enter this into your REPL and then try and call it, you'll get a mystifying result:

当你把它输入你的REPL,然后尝试调用它,你会得到一个神秘的结果:

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

This is because the presence of yield signaled to Python that you want to create a generator, that is, an object that generates values on demand.

这是因为yield对Python发出了信号,您希望创建一个生成器,即根据需要生成值的对象。

So, how do you generate these values? This can either be done directly by using the built-in function next, or, indirectly by feeding it to a construct that consumes values.

那么,如何生成这些值呢?这可以通过使用内置函数进行直接操作,也可以通过将其提供给一个使用值的构造来间接完成。

Using the built-in next() function, you directly invoke .next/__next__, forcing the generator to produce a value:

使用内置的next()函数,您可以直接调用.next/__next__,迫使生成器产生一个值:

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

Indirectly, if you provide fib to a for loop, a list initializer, a tuple initializer, or anything else that expects an object that generates/produces values, you'll "consume" the generator until no more values can be produced by it (and it returns):

间接地,如果您将fib提供给for循环,一个列表初始化器,一个tuple初始化器,或者其他任何期望一个产生/产生值的对象,您将“消耗”生成器,直到它不再产生更多的值(并且它返回):

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

Similarly, with a tuple initializer:

类似地,使用tuple初始化器:

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

A generator differs from a function in the sense that it is lazy. It accomplishes this by maintaining it's local state and allowing you to resume whenever you need to.

生成器与函数的不同之处在于它是懒惰的。它通过维护本地状态来实现这一点,并允许您在需要时恢复。

When you first invoke fib by calling it:

当您第一次调用fib时,调用它:

f = fib()

Python compiles the function, encounters the yield keyword and simply returns a generator object back at you. Not very helpful it seems.

Python编译函数,遇到yield关键字,然后简单地返回一个生成器对象。似乎没有多大帮助。

When you then request it generates the first value, directly or indirectly, it executes all statements that it finds, until it encounters a yield, it then yields back the value you supplied to yield and pauses. For an example that better demonstrates this, let's use some print calls (replace with print "text" if on Python 2):

当您请求它生成第一个值时,它会直接或间接地执行它所找到的所有语句,直到它遇到一个yield,然后它会返回您提供给yield和停顿的值。为了更好地演示这一点,让我们使用一些打印调用(如果在Python 2中,用print“text”替换):

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

Now, enter in the REPL:

现在,进入REPL:

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

you have a generator object now waiting for a command for it to generate a value. Use next and see what get's printed:

您现在有一个生成器对象,等待它生成一个值的命令。使用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!'

The unquoted results are what's printed. The quoted result is what is returned from yield. Call next again now:

未引用的结果是打印出来的。所引用的结果是由yield返回的结果。现在再次调用下一个:

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

The generator remembers it was paused at yield value and resumes from there. The next message is printed and the search for the yield statement to pause at it performed again (due to the while loop).

生成器记得它是在yield值和从那里恢复的时候暂停的。下一个消息被打印出来,并且搜索yield语句以在它再次执行时暂停(由于while循环)。

#28


26  

Many people use return rather than yield but in some cases yield can be more efficient and easier to work with.

许多人使用回报而不是收益,但在某些情况下,收益可以更有效和更容易使用。

Here is an example which yield is definitely best for:

这里有一个例子,绝对是最好的:

return (in function)

返回(函数)

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

yield (in function)

收益率(函数)

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

Calling functions

调用函数

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)

Both functions do the same thing but yield uses 3 lines instead of 5 and has one less variable to worry about.

两个函数都做同样的事情,但是收益率用3行而不是5,并且有一个更少的变量需要担心。

This is the result from the code:

这是代码的结果:

“yield”关键字是做什么用的?

As you can see both functions do the same thing, the only difference is return_dates() gives a list and yield_dates() gives a generator

当您看到两个函数都做相同的事情时,唯一的区别是return_dates()给出了一个列表,而producd_date()提供了一个生成器。

A real life example would be something like reading a file line by line or if you just want to make a generator

一个真实的例子就像是逐行读取一个文件,或者你只是想做一个生成器。

#29


25  

In summary, the yield statement transforms your function into a factory that produces a special object called a generator which wraps around the body of your original function. When the generator is iterated, it executes your function until it reaches the next yield then suspends execution and evaluates to the value passed to yield. It repeats this process on each iteration until the path of execution exits the function. For instance;

综上所述,yield语句将您的函数转换为一个工厂,该工厂产生一个称为生成器的特殊对象,该对象包围您原始函数的主体。在迭代生成器时,它将执行您的函数,直到它到达下一个yield,然后暂停执行并计算传递给yield的值。它在每次迭代中重复这个过程,直到执行路径退出该函数。例如;

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

for i in simple_generator():
    print i

simply outputs ;

简单的输出;

one
two
three

The power comes from using the generator with a loop that calculates a sequence, the generator executes the loop stopping each time to 'yield' the next result of the calculation, in this way it calculates a list on the fly, the benefit being the memory saved for especially large calculations

功率来自于使用一个循环来计算一个序列,生成器执行循环停止每一次以“产生”计算的下一个结果,以这种方式计算一个列表在苍蝇上,好处是为特别大的计算节省的内存。

Say you wanted to create a your own range function that produces an iterable range of numbers, you could do it like so,

假设你想创建一个你自己的range函数它产生一个可重复的数字范围,你可以这样做,

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

and use it like this;

像这样使用;

for i in myRangeNaive(10):
    print i

but this is ineffecient because

但这是无效的,因为。

  • You create an array that you only use once (this wastes memory)
  • 创建一个只使用一次的数组(这浪费内存)
  • This code actually loops over that array twice! :(
  • 这个代码实际上会循环遍历该数组两次!:(

Luckily Guido and his team were generous enough to develop generators so we could just do this;

幸运的是,圭多和他的团队非常慷慨地开发了发电机,所以我们可以这样做;

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

for i in myRangeSmart(10):
    print i

Now upon each iteration a function on the generator called next() executes the function until it either reaches a 'yield' statement in which it stops and 'yields' the value or reaches the end of the function. In this case on the first call, next() executes up to the yield statement and yield 'n', on the next call it will execute the increment statement, jump back to the 'while', evaluate it, and if true, it will stop and yield 'n' again, it will continue that way until the while condition returns false and the generator jumps to the end of the function.

现在,在每个迭代上,生成器调用next()函数执行该函数,直到它到达“yield”语句为止,该语句停止并“生成”值或到达函数的末尾。在这种情况下,在第一次调用时,()执行声明收益率和收益率“n”,在下一个叫它将执行增量声明,跳回“同时”,评估,和如果这是真的,它将停止和产量再次“n”,将继续,直到条件返回false和发电机跳到函数的结束。

#30


18  

Yet another TL;DR

另一个TL,博士

iterator on list: next() returns the next element of the list

列表中的迭代器:next()返回列表的下一个元素。

iterator generator: next() will compute the next element on the fly (execute code)

迭代器生成器:下一个()将计算苍蝇的下一个元素(执行代码)

You can see the yield/generator as a way to manually run the control flow from outside (like continue loop 1 step), by calling next, however complex the flow.

您可以将yield/generator视为手动运行外部控制流的一种方式(如继续循环1步),通过调用next,不管流程多么复杂。

NOTE: the generator is NOT a normal function, it remembers previous state like local variables (stack), see other answers or articles for detailed explanation, the generator can only be iterated on once. You could do without yield but it would not be as nice, so it can be considered 'very nice' language sugar.

注意:生成器不是一个正常的函数,它会记住以前的状态,比如本地变量(stack),查看其他的答案或文章来进行详细的解释,生成器只能一次迭代。你可以不屈服,但它不会那么好,所以它可以被认为是“非常好的”语言糖。