Python 中产出关键字的用法是什么? 它能做什么?

例如,我试图理解这个代码1:

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

这就是打电话的人:

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

当方法 _get_child_camedates 被调用时会怎样? 列表是否返回? 单一个元素吗? 是否再次调用? 以后的电话何时停止?


1. 本代码由Jochen Schulz(jrschulz)编写,他为公制空间制作了一个伟大的Python图书馆,与完整的源:模块mspace链接。


收益率和返回一样, 它会返回任何您告诉它的东西( 作为生成器 ) 。 区别在于下次您调用生成器时, 执行从最后一次调用开始到收益语句 。 与返回不同的是, 当收益发生时, 堆叠框架不会被清理, 但是控制会被转回调回调用方, 因此下次调用函数时, 它的状态将会恢复 。

在您的代码中,函数获取_child_camedates 的动作就像一个迭代器,这样当您扩展列表时,它会一次在新列表中添加一个元素 。

列表。extendend calls a plerator until it's fulled it's explator until. 如果是您所贴的代码样本, 只需将图普还给列表, 并附加到列表中, 就会更加清楚 。


我不太熟悉Python, 但我相信它和C#的传动屏障一样, 如果你熟悉这些。

关键的想法是, 编译者/ 解释者/ 不论做什么诡计, 就打电话者而言, 他们可以继续调用下一个 () , 并且它会继续返回数值 - 仿佛生成器方法被暂停。 现在显然你无法真正“ 暂停” 一种方法, 所以编译者可以建立一个状态机器, 以便您记住您目前的位置和本地变量等的外观 。 这比自己写一个转录器容易得多 。


这样想吧:

迭代器只是具有下一个( ) 方法的对象的奇特探测术语。 因此, 产生式的函数最终会变成这样 :

原文:

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

for i in some_function():
    print i

Python 翻译用上述代码所做的基本上就是:

class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i

为了更深入地了解幕后发生的事情,

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

这更有意义还是更让人困惑?

我要指出,为了说明起见,这过于简单化。 )


要了解什么是产量,你必须了解什么是发电机。在你能够理解发电机之前,你必须了解易燃的发电机。

易变性

创建列表时,您可以逐项阅读其项目。逐项阅读其项目被称为迭代:

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

My list 是可替换的。 当您使用列表理解时, 您会创建一个列表, 因而是一个可替换的 :

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

你可以使用的一切"... 在..."是一个可循环的; 列表,字符串,文件...

这些可替换的功能是实用的,因为您可以随心所欲地阅读,但您将所有值都存储在记忆中,当您拥有很多值时,这并不总是你想要的。

发电机发电机

发电机是迭代器, 一种可迭代的循环, 您只能循环一次 。 发电机不会存储记忆中的所有值, 它们会在苍蝇上生成值 :

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

除了使用()而不是使用()之外,它是一样的。但是,由于发电机只能使用一次,所以不能在我的生成器中为我第二次执行,因为发电机只能使用一次:它们计算0,然后忘记它,然后计算1,然后结束计算4,一个一个一个地计算。

产量d

函数将返回一个生成器。

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

这是一个毫无用处的例子, 但当你知道你的功能会返回 一大堆的值时, 它就方便了, 你只需要读一次。

要掌握输出能力, 您必须明白当您调用函数时, 您在函数体中写入的代码没有运行。 函数只返回生成对象, 这有点棘手 。

然后,你的代码会继续 从它离开的每一次 使用发电机。

现在,硬的部分:

第一次调用您函数所创建的生成器对象时, 它会运行您函数的代码, 从开始到它产生, 然后返回循环的第一个值。 然后, 以后每次调用都会运行您在函数中写入的循环的再次迭代, 然后返回下一个值。 这将一直持续到生成器被认为是空的, 当函数运行时不会打出收益。 这可能是因为循环结束, 或者因为您不再满足“ if/ else ” 。


您的代码解释

发电机:

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

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

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

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

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

调用者 :

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

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

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

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

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

    # Add the children of the candidate to the candidate's list
    # so the loop will keep running until it has 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

本代码包含几个智能部分 :

在列表中循环迭代, 但列表会随着循环迭代而扩展。 这是一个简单的方式来查看所有这些嵌套的数据, 即使它是一个有点危险的, 因为您可以以无限环结束。 在此情况下, 候选人 。 extendend( rode._ get_ child_ camedates( root, min_ dist, max_ distist)) 将耗尽所有生成器的值, 但同时继续创建新生成的生成对象, 这些对象将产生与先前的相异的值, 因为它不会被应用到同一个节点上 。 扩展 () 方法是一种列表对象方法, 期待一个可重复的列表对象方法, 并将其添加到列表中 。

通常,我们向它传递一份清单:

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

但在你的代码中,它有一个发电机, 这是很好的,因为:

你不需要两次阅读这些值。 你可能有很多孩子, 你不想把他们都保存在记忆中。

之所以有效,是因为 Python 并不在意一种方法的论据是否是一个列表。 Python 期望它能用字符串、列表、图普勒和生成器来操作。 这叫做鸭字打字, 也是Python之所以如此酷的原因之一。 但是这是另一个故事, 另一个问题...

您可以在这里停下来,或者读一下,看一个生成器的先进使用:

控制发电机耗竭

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

注: Python 3, 使用打印( corner_street_atm._next___ ()) 或打印( ext( corner_ street_ atm) )

它可以对控制获取资源等各种事情有用。

义大便,你最好的朋友

Itertool 模块包含操作可替换文件的特殊功能 。 是否想要复制一个生成器? 连锁二生成器? 组值在单行的嵌套列表中? 地图/ Zip 不创建另一个列表 ?

然后就进口它的工具。

举个例子,让我们看看四匹马赛的到货订单

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

了解迭代的内部机制

迭代是一个过程, 意味着可迭代( 实施 _ etre_ () 方法) 和迭代( 实施 ext_ () 方法) 。 迭代是您可以从中获取迭代器的任何对象。 迭代器是允许您循环到可迭代的物体 。

本文中有更多关于环环如何运作的论述。


下面是浅白语言的例子。我将提供高层次人类概念与低层次Python概念之间的对应关系。

我想用数字序列操作, 但我不想用这个序列的创建来烦恼我自己, 我只想专注于我想做的操作。 因此, 我做以下工作:

我打电话给你并告诉你,我想要一个以特定方式计算的数字序列,我告诉你算法是什么。 这个步骤对应着定义发电机的函数, 也就是包含一个产出的函数。 稍后我告诉你, “ 好, 准备好告诉我数字的序列 ” 。 这个步骤对应着调用发电机的函数, 返回一个发电机对象。 注意不要告诉我任何数字; 你只是拿起你的纸张和铅笔。 我问你, “ 请告诉我下一个数字 ” , 然后你告诉我第一个数字; 之后, 你等着我问你下一个数字。 这是你的任务, 也就是确定你所在的位置, 你已经说过的数字, 下一个数字是什么。 我不在乎细节。 这个步骤相当于在发电机对象上调用下一个( 发电机) 号码的方法。 ( Python 2, next) 注意, 这是一个发电机对象的方法; 在 Python 3, 它被命名为...

这是生成器所做的( 包含一个产值的函数 ) ; 它开始在第一个( ) 上执行, 当它做一个产值时暂停, 当要求下一个( ) 值时, 它会从最后一点继续 。 它的设计完全符合 Python 的循环协议, 协议描述如何按顺序要求值 。

迭代协议最著名的用户是 Python 的命令用户。 所以, 当你做 :

for item in sequence:

序列是否是一个列表、字符串、字典或上述生成对象并不重要;结果是一样的:您逐个阅读序列中的项目。

请注意,定义含有产出关键字的函数不是创建生成器的唯一方法;它只是创建生成器的最简单的方法。

欲知更准确的信息,请阅读Python文件中的迭代机类型、产量说明和发电机。


还有一件事情要提: 产量的函数其实不一定要终止。我写了这样的代码:

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

这样我就可以用在别的代码里了

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

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


理解产出的快捷键

当您看到带产出语句的函数时,应用这个简单易懂的把戏来理解会发生什么:

在函数开始处插入行结果 = []。 以结果替换每个输出。 附录( 扩展) 。 在函数底部插入一行返回结果 。 耶 - 不再生成语句! 读取并解析代码。 将函数与原始定义比较 。

这个骗局也许能让你了解函数背后的逻辑, 但实际的收益率与列表法中发生的情况大不相同。 在许多情况下, 收益率法会提高记忆效率和速度。 在其他情况下, 这个骗局会让你陷入一个无限的循环, 即使最初的功能运作良好。 阅读以学习更多...

不要弄乱你的循环器 循环器和发电机

首先,当您写作时的循环程序协议

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

Python 执行以下两个步骤:

为我的列表获取一个代号 : 调用 exer( mylist) - > 这返回一个具有下一个( ) 方法( 或 __ next__ () () 在 Python 3 中) 的对象 [这是大多数人忘记告诉你 使用传动器环绕项目的步骤 : 继续调用从第 1 步返回的代名器上的下一个( ) 方法 。 下一个( ) 的返回值被指定给 x , 循环体被执行 。 如果从下一个( ) 中提出例外 停止 , 这意味着在循环器中没有更多的值, 循环被退出 。

真相是 Python 执行上述两个步骤, 每当它想绕过对象的内容时, 都会执行上述两个步骤 - 所以它可以是环绕, 但它也可以像其它列表一样是代码 。 extendend( mylist) ( 其中其他列表是 Python 列表 ) 。

这里的我的列表是可替换的, 因为它执行的是循环协议 。 在用户定义的类别中, 您可以使用 ` iter__ () 方法使分类的循环性实例可以被使用。 此方法应该返回一个循环器。 循环器是一个带有下一个( ) 方法的对象。 在同一类中可以同时执行 _ iter__ () 和 下一个( ) , 并有 _ iter__ () 返回自我 。 这将对简单案例有效, 但当您想要两个循环器同时绕过同一个对象时则不行 。

这就是传动程序,许多物体执行这个程序:

内置列表、 词典、 图普尔、 集和文件。 执行 ` iter__ () 的用户定义的分类 。 发电机 。

注意“ 循环” 并不知道它所处理的物体是什么类型 - 它只是遵循了循环程序, 并且乐意在下一个( ) 调用时按项目逐项获得项目 。 内建列表逐项返回项目, 字典逐项返回关键词, 文件逐行返回行等 。 而发电机则返回... 也就是产出来源所在 :

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

而不是输出语句, 如果您在 f123 () 中有三个返回语句, 只有第一个将被执行, 而函数会退出 。 但是 f123 () 并不是普通函数 。 当调用 f123 () 时, 它不会返回产值语句中的任何值 。 它返回一个生成对象 。 另外, 该函数并不真正退出 - 它会进入一个中止状态 。 当循环尝试在生成对象上循环时, 函数会从先前返回的产值之后的下一行的中止状态恢复到下一行的状态, 执行下一行代码, 在此情况下, 产生语句, 并返回为下一个项目 。 这一直发生到函数退出, 此时, 生成器将启动暂停, 以及循环退出 。

因此,生成器对象有点像一个适配器 — — 在一端,它展示了迭代程序, 暴露了 `iter___ () 和下一个 () 方法来保持循环的快乐。 但是,在另一端, 它运行着功能, 足以将下一个值调出, 并把它放回中止模式 。

为什么使用发电机?

通常情况下, 您可以写入不使用发电机的代码, 但执行相同的逻辑。 一个选项是使用我之前提到的临时列表“ trick ” 。 这不会在所有情况下都有效, 比如, 如果您有无限环, 或者当您有非常长的列表时它可能无效地使用内存 。 另一种方法是执行一个新的可循环的类别“ 某些东西 ” , 将国家保留在成员中, 并在下一个( ) ( 或 Python 3 ) 方法中执行下一个逻辑步骤 。 根据逻辑, 下一个( ) 方法中的代码可能最终会查找非常复杂和易被错误的代码 。 在这里, 生成器可以提供一个简单明了的解决方案 。


产出关键字简化为两个简单的事实:

如果编译者在函数内的任何地方检测到产出关键字,则该关键字函数不再通过返回语句返回。相反,它会立即返回一个称为生成器的懒惰的“待决列表”对象。“生成器”是可循环的。什么是可循环的?它像列表或设置或范围或编辑视图一样,带有按一定顺序访问每个元素的内置协议。

简言之: 最常见的情况是, 发电机是一个懒惰的、 递增的等待列表, 并且产出语句允许您使用函数符号来编程生成器应该逐渐吐出的列表值。 此外, 高级用法允许您使用发电机作为共程( 见下文 ) 。

generator = myYieldingFunction(...)  # basically a list (but lazy)
x = list(generator)  # evaluate every element into a list

   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

基本上, 只要遇到产出语句, 函数就会暂停并保存状态, 然后根据 Python 传动协议( 在某些合成结构中, 类似反复呼叫下一个( ) 的循环, 并捕捉一个停止作用的例外等) , 发出“ 列表中的下一个返回值 ” 。 您可能遇到过带有生成表达式的生成器; 生成函数更强大, 因为您可以将参数反馈到暂停的生成器功能中, 使用它们来实施 comutines 。 稍后会更多 。


基本示例(“清单”)

我们来定义一个函数,它就像 Python 的射程。 调用 makeRange(n) returns a Generator:

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

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

要强制生成器立即返回其未完成的值, 您可以将它传送到列表 () (就像您可以任意使用 ) :

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

比较“仅返回列表”的示例

上述例子可视为仅仅是创建一份清单,并附在后面并返回:

# return a list                  #  # return a generator
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
    return TO_RETURN             # 

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

不过,有一个重大差别;见最后一节。


您如何使用发电机

所有发电机都是易变的, 所以它们经常被这样使用:

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

要对发电机有更好的感觉,您可以玩过工具模块( 一定要使用链。 来自_ irightable, 而不是当必要时使用链子 ) 。 例如, 您甚至可以使用生成器来实施无限长的懒惰列表, 比如 ltertools. counts () 。 您可以执行您自己的除法列表( 可认证的) : zip( 计数 ) , 或使用一段时间内生成关键字来这样做 。

请注意: 发电机实际上可以用于更多的事情, 比如实施 comotines 或非 确定性编程或其他优雅的东西。 然而, 我在此介绍的“ 懒惰列表” 观点是您最常用的 。


幕后幕后

这就是“ Python 迭代协议” 的原理。 也就是说, 当您做列表( makeRange(5)) 时会发生什么 。 这就是我前面描述的“ 懒惰、 递增列表 ” 。

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

内建函数的下一个 () 只是调用对象 ._next__ () 函数, 这是“ 电路协议” 的一部分, 并在所有迭代器中找到 。 您可以手动使用下一个( ) 函数( 和迭代协议的其他部分) 来执行奇特的东西, 通常以降低可读性为代价, 所以尽量避免这样做...


锥体

圆柱形示例:

def interactiveProcedure():
    userResponse = yield makeQuestionWebpage()
    print('user response:', userResponse)
    yield 'success'

coroutine = interactiveProcedure()
webFormData = next(coroutine)  # same as .send(None)
userResponse = serveWebForm(webFormData)

# ...at some point later on web form submit...

successStatus = coroutine.send(userResponse)

comotine (generations 通常接受通过 产出 关键字输入输入 , 例如, 下一个 Input = 产生下一个输出, 作为一种双向通信形式) 基本上是允许暂停自己和请求输入的计算( 例如它下一步应该做什么 ) 。 当 comotine 暂停自己( 当运行中的 comotine 最终会点击 产出 关键字时) , 计算会暂停, 控制会被倒回“ 调” 函数( 要求下一个计算值的框架 ) 。 暂停的发电机/ 库外 仍然暂停, 直到另一个函数( 可能是一个不同的函数/ 文本) 启用后, 请求下一个值( 通常通过输入数据将暂停的逻辑内部输入到 comutine 的代码 ) 。

您可以将皮延共程视为懒惰的递增待决列表, 下一个元素不仅取决于先前的计算, 而且还取决于输入, 您可以选择在生成过程中注射 。


贫提亚e

通常,大多数人不会关心以下的区别,可能想在这里停止阅读。

在 Python-speak 中, 迭代是“ 理解“ 循环概念” 的任何物体, 如列表[ 1, 2, 3] , 转动器是请求循环的具体实例, 如 [ 1, 2, 3,. . _ eter __ () 。 生成器与任何迭代器完全相同, 但它的写法除外( 带有函数语法 ) 。

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

因此,在不可能的情况下,你没有 做这样的事情...

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

... 然后记住发电机是一个迭代器, 也就是说, 它是一次性使用。 如果您想要再使用它, 您应该再次调用 MyRange (...) 。 如果您需要使用结果两次, 将结果转换为列表, 并存储在变量 x = 列表( MyRange (5)) 中。 那些绝对需要克隆生成器的人( 例如, 那些正在做可怕的黑客化元程序设计的人) 可以使用它的工具.tee( 仍然在 Python 3 中工作) , 如果绝对需要的话, 因为可复制的迭代器 Python PEP 标准建议已被推迟 。


以下是一些Python的例子, 说明如何实际安装发电机, 仿佛Python没有提供同声糖:

作为Python发电机:

from itertools import islice

def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

使用地法关闭代替发电机

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

使用关闭物体而不是发电机(因为关闭物体和物体是等效的)

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)

失败给了你一台发电机

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

正如你可以看到的那样,在第一种情况下,Foo同时持有完整的记忆列表。对于包含5个元素的清单来说,这不是什么大不了的事,但是如果你想列出500万个元素的清单,那又会怎样?这不仅仅是一个巨大的记忆食用器,在函数被调用的时候,它还要花费很多时间来构建这个功能。

在第二种情况下, 酒吧只给您一台发电机。 发电机是一个可循环的, 意思是您可以在循环中使用它, 等等, 但每个值只能存取一次。 所有值也并非同时存储在记忆中; 生成器的“ 成员” 对象, 上次您称之为“ 成员” 时, 它在循环中。 这样, 如果您使用一个可( 说) 的转号, 计数为500亿, 你不必一次数到500亿, 然后存储500亿的数值来进行计算。

再者,这是一个相当巧妙的例子,如果你真想数到500亿,你可能会使用滑板。 () :

这是发电机中最简单的使用实例。 正如您所说, 它可以用来写高效的变换, 使用产量将东西推到调用堆叠上, 而不是使用某种堆叠变量。 发电机也可以用于专门的树道, 以及各种其它方式 。


对于那些更喜欢最低限度工作实例的人来说,考虑一下这次交互式的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

我本打算张贴“Beazley的“Python:基本参考”第19页,

另外,请注意, 产量可以作为发电机功能中的双重用途, 共程中可以使用。 虽然这与您的代码片断不相同, 但( ield) 也可以用作函数中的表达方式。 当调用者使用发送( ) 方法给方法发送一个值时, 共程将执行到遇到下一个( yeld) 语句时 。

生成器和共同路由是建立数据流类型应用程序的很酷的方法。 我认为值得知道在函数中产出语句的另一种用途。


在描述如何使用发电机的许多伟大答案中, 我感到还没有给出一种答案。 这是编程语言理论的答案:

Python 中的收益率语句返回了一个发电机。 Python 的发电机功能返回了连续性( 具体地说, 是一种共同的常规, 但连续性代表了了解情况的一般机制 ) 。

编程语言理论的继续是更根本的计算方法,但通常不会被使用,因为它们极难解释,也很难执行。但是,关于继续的理念很简单:是计算状态尚未完成。在这种状态下,变量的当前值、尚未执行的操作等等被保存。然后,在程序稍后的某个时候,可以援引继续,使程序的变量被重新设置到状态,保存的操作被执行。

以这种更一般的形式, 延续可以用两种方式执行 。 以调用/ cc 方式, 程序堆放的堆放被实际保存, 然后当继续使用时, 堆放被恢复 。

在继续传承风格(CPS)中,续编只是程序员明确管理和传到子例程的正常功能(仅在功能为头等语言的语文中),程序员明确管理和传到子例程。在这种风格中,程序状态代表关闭(和恰好在其中编码的变量),而不是堆叠中某处的变量。 管理控制流程的功能接受继续作为参数(在CPS的某些变异中,功能可能接受多重延续),并通过仅拨打这些函数来操纵控制流程,然后返回。一个非常简单的延续传承风格实例如下:

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

  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

在此(非常简单化的)示例中,程序员将实际写入文件的操作保存为续存(这有可能是一个非常复杂的操作,有许多细节要写出来),然后将这一续存(即作为头等关闭)传递给另一个操作员,该操作员会做一些更多的处理,然后在必要时调用它。 (在实际的 GUI 编程中,我大量使用这种设计模式,要么是因为它可以节省我的代码线,要么更重要的是,在图形用户界面事件触发后管理控制流程。 )

这个职位的其余部分将不失为一般性,将连续性概念化为CPS, 因为它很容易理解和阅读。

现在让我们来谈谈Python 的发电机。 发电机是一种特定的子延续类型。 虽然继续一般能够保存计算状态( 即程序调用堆) , 但发电机只能保存循环器的循环状态 。 虽然这个定义对于发电机的某些使用案例来说有点误导 。 例如 :

def f():
  while True:
    yield 4

这显然是一个合理的可循环性,其行为是明确的 -- 每次发电机在发电机上转动时,它就会返回 4 (并永远这样做 ) 。但是,在考虑迭代器时,它可能并不是一种典型的可循环的类型(例如,收藏中的x:Do_hine(x) ) 。 这个例子说明了发电机的力量:如果有什么是迭代器,一个发电机可以保存其迭代状态。

需要重申: 继续可以保存程序堆叠的状态, 发电机可以保存循环状态 。 这意味着继续能力比发电机强大得多, 同时发电机也容易得多。 语言设计师更容易实施,程序设计员更容易使用( 如果您有时间燃烧, 试着读懂和理解关于继续和调用/ cc的页面 ) 。

但您可以很容易地实施(和概念化)发电机,作为延续传承风格的一个简单而具体的例子:

当调用产值时, 它会告诉函数返回一个延续。 当再次调用函数时, 它从它所剩的开始。 所以, 在伪假伪代码( 即不是伪代码, 但不是代码) 中, 生成器的下一个方法基本上如下 :

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

当产出关键字实际上为实际生成功能的合成糖时, 基本上是类似 :

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

记住这只是假码,而Python发电机的实际安装则更为复杂。 但是,为了了解正在发生的事情,试图使用持续的传记风格来实施生成器,而不使用产出关键字。


这是关于产量的心理形象。

我想把一条线视为有堆叠(即使它不是用这种方式执行的)。

当调用一个普通函数时, 它会将其本地变量放入堆栈, 进行一些计算, 然后清除堆栈和返回。 其本地变量的值再也不会被看到 。

当它的代码开始运行时( 即函数被调用后, 返回一个生成对象, 其下一个( ) 方法随后被引用) , 它同样将其本地变量放在堆叠上, 并进行一段时间的计算 。 但是, 当它点击收益语句时, 在清理堆叠中的一部分并返回之前, 它会对其本地变量进行截图, 并将其存储在生成对象中 。 它还写下它目前位于代码中的位置( 即特定收益语句 ) 。

所以这是一种冷冻功能 发电机挂在了上面

当下一个 () 后被调用时, 它会从堆栈上取回函数的物品, 并重新激活它。 函数继续从剩余部分进行计算, 无视它刚刚在冷藏室里度过了一个永恒的时间 。

比较以下实例:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

当我们称第二个函数为第二个函数时, 它的行为与第一个函数非常不同。 收益声明可能无法达到, 但是如果它存在任何地方, 它会改变我们正在处理的东西的性质 。

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

调用 powerFunction () 不运行它的代码, 而是让一个生成器出自代码 。 (也许用 powerFunction () 的前缀命名这种东西作为可读性是一个好主意 。)

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

gi_ code 和 gi_ frame 字段是存放冻结状态的地方。 使用 dir (.) 来探索它们, 我们可以确认上面的心理模型是可信的 。


从方案拟订的角度来看,迭代器是作为散装件执行的。

为实施同时执行的迭代器、发电机和线形集合等,人们使用发往有调度员的关闭对象的电文,用发件人对“信息”的回答。

"下一步"是给一个封口发送的信息 由"标准"电话创建

有多种方法可以实施此计算。 我使用了突变, 但可以通过返回当前值和下一个生成者( 使其具有优先透明度 ) , 进行这种不发生突变的计算。 鼠标使用一些中间语言对初始程序进行一系列转换, 其中之一是将产出操作者转换为使用更简单的操作员的某种语言。

这是如何重写产量的演示, 它使用 R6RS 的结构, 但语义与 Python 的相同 。 这是相同的计算模式, 只需要修改语法, 才能使用 Python 的 产量重写 。

- (define gen (lambda (l) (define gen (lambda (l)) (define emple (lambda (lambda () ()) (if (null? l)) 'END (let ((v (car l))(set))(l (cdr))) (lambda (m) (cket m) (case m ('yield (yeld)(yeld))('ield))('iint (lamb) (lambda (lab) (lambda (data) (data) (l data))) ())) ) - (define 流 (gen 'ield (gen'(1,2 3 ) )) - (流 (流 ield) ) ) - (Live END - (Slead) (流 (流 ) (流 ) (流 (流 流 (流 流 流 流 流 流 ) 'ield) 'end - >


以下是一个简单的例子:

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

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

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

产出:

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

我不是Python开发者,但对我来说,它似乎保持了程序流程的位置,而下一个循环则从“当量”的位置开始。 它似乎正在等待着这个位置,就在那个位置之前,它正在向外回报一个价值,而下一次将继续工作。

这似乎是一个有趣和好的能力:


虽然很多答案都表明你为什么用一个产量来创建发电机, 但对于产量来说却有更多的用途。 做一个共同的习惯很容易, 这样可以让信息在两个代码区块之间传递。 我不再重复任何已经给出的关于使用产量来创建发电机的好例子 。

为了帮助理解下一个代码中的产值, 您可以用手指通过任何有产值的代码来追踪循环。 每次你的手指碰到产量的代码时, 您必须等待下一个或发送输入。 调用下一个代码时, 您可以跟踪代码, 直到你到达产量... 产值右侧的代码被评估并返回到调用者... 然后您等待。 当调用下一个代码时, 您会通过代码执行另一个循环。 但是, 您会注意到, 产值也可以用一个发送方式来使用。 发送时, 将会将一个调用器的值发送到产值中 。 如果发送了, 则会收到发送的值, 然后将其从左侧吐出... , 然后通过代码的跟踪直到您再次到达产量( 返回最后的值, 如下一个调用 ) 。

例如:

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

产量的另一种用途和含义(自Python3.3以来):

yield from <expr>

PEP380 -- -- 属于子发电机的语法:

提议对发电机使用语法将部分操作权下放给另一个发电机。 这样可以将含有“ ield” 的代码部分输入到另一个发电机中。 此外, 允许次发电机返回一个值, 并将价值提供给授权发电机。 新的语法也为当一台发电机再生价值时实现优化开辟了某些机会 。

此外,这将引入(自Python3.5以来):

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

以避免与正常发电机混为一谈(两者均使用每天的产量)。


输出是函数的返回元素。 区别在于, 产出元素将函数转换成生成器。 生成器的行为就像一个函数, 直到某种“ 归属 ” 。 生成器停止直到下一次调用, 并且从与开始的完全相同的点继续。 您可以通过调用列表( 生成器 () ) 获得一个序列中所有“ 属性” 值的序列。


Python 的输出关键字是做什么的 ?

答复大纲/摘要

函数, 调用时, 返回生成器。 发电机是循环器, 因为它们执行循环程序, 以便您可以对它进行循环。 也可以发送一个发电机信息, 使其在概念上成为共同的常规。 在 Python 3 中, 您可以将一个发电机从一个发电机到另一个发电机, 从两个方向调用。 (附录: 包括顶部的答案在内的几个答案, 并讨论在发电机中使用返回的方法 。)

发电机:

收益率只是功能定义中的法律内涵,而将收益率列入功能定义使其返回产生者。

发电机的想法来自其他语言(见脚注1),其实施方式各有不同。 在Python的发电机中,代码的执行在生产点被冻结。当发电机被调用(方法在下文讨论)时,再恢复执行,然后冻结在下一个生产点。

输出提供了执行循环协议的简单方法,由以下两种方法定义:__iter__和__ext_。这两种方法都使对象成为可与收藏模块的Exerator摘要基础类进行打印的复制器。

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__
True                           # implements the iterator protocol.

生成器类型是一个子迭代器类型 :

from types import GeneratorType
from collections.abc import Iterator

>>> issubclass(GeneratorType, Iterator)
True

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

>>> isinstance(gen, GeneratorType)
True
>>> isinstance(gen, Iterator)
True

迭代器的一个特征是,一旦耗竭,您无法再利用或重置它:

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

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

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

可以按方案生成数据,例如:

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

上述简单生成器也相当于以下生成器 -- -- 由于Python 3.3, 您可以使用以下来源的产量:

def func(an_iterable):
    yield from an_iterable

但是,也允许向次级发电机授权,这一点将在下一节 " 与次级水泥合作授权 " 中加以解释。

计票:

窗体中显示一个表达式,该表达式允许将数据发送到生成器(见脚注3)

以下是一个例子,请注意收到的变量,该变量将指向发送到生成方的数据:

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)

首先, 我们必须排队, 下一个是内建函数 。 它会调用合适的下一个或 下一步方法, 取决于您使用的 Python 版本 :

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

现在我们可以将数据发送到生成器。 (“终结者”和“下一个”是一样的 ) :

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

合作社代表团到分科诊所分科

现在,请记住,Python 3的产量是可以得到的。 这使得我们可以将共同路线 委托给一个子烹饪:


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

def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    # must queue up manager:
    next(manager)      # <- same as manager.send(None)
    # This is where we send the initial deposit to the manager:
    manager.send(deposited)
    try:
        yield from manager
    except GeneratorExit:
        return manager.close()  # delegate?

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

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

现在模拟在账户中再增加1000, 加上账户的回报( 60.0 ) :

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

从PEP 380中,您可以阅读更多关于产量的确切语义。

其他方法:关闭和投掷

关闭方法在功能执行被冻结时提升发电机输出。 也可以被 __ del__ 调用, 这样您就可以设置任何清理代码, 用于处理发电机输出 :

my_account.close()

您也可以丢弃一个例外,该例外可在生成器中处理,或向用户传播:

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

提高:

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

结论 结论 结论 结论 结论

我认为,我已处理了下列问题的所有方面:

Python 的输出关键字是做什么的 ?

事实证明,产量是很大的。我相信我可以为此再增加更详尽的例子。如果你需要更多的或有建设性的批评,请在下面评论,让我知道。


附录:

顶级/接受的答复的优先程度**

使用列表作为示例。 参见我上面的引用, 但概括地说: 循环含有 ` irit_ 的方法返回一个迭代器。 一个迭代器另外提供了一种 . next_ 的方法, 以循环为暗号, 以循环为代号, 直到它升起 停止 试运行, 一旦它确实升起 停止 试运行, 它会继续这样做 。 然后它会使用一个发电机表达方式来描述一个发电机。 由于一个发电机表达方式只是创建一个代用器的方便方式, 它只会混淆物质, 而我们还没有到达产值部分 。 在控制发电机耗竭时, 他调用 . next 方法( 只在 Python 2 中有效 ) , 而不是使用 内建函数, 下一步。 调用下一个 (obj) 将是一个适当的间接层, 因为他的代码在 Python 3. Itertools 中不起作用 。 这与结果完全无关 。 没有讨论 与 Python 3 中产生新功能收益的方法提供的方法和 Python 。

上方/接受的回答是一个非常不完整的回答。

回答的精度表示在发电机的表达或理解中产生产量。

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

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

由于产量是一种表达方式,有些人认为在理解或生成方表达方式中使用产量是令人感兴趣的,尽管没有提出特别好的使用方式。

CPython核心开发商正在讨论其备抵的折旧问题。

2017年1月30日19:05时,布雷特坎农写道:在太阳上,2017年1月29日,16:39克雷格·罗德里格斯写道:我同意这两种方法。把事情保留在Python 3的状态是不对的,IMHO。我的投票是语法错误,因为你没有得到你期望的语法。我同意这对我们来说是一个明智的结局,因为任何依赖当前行为的代码都非常聪明,无法维持。在到达那里时,我们可能想要:在2.7的Py3k警告中,用3.7的Py3k警告来表示警告或破坏警告。x语法错误,Nick。 -- Nick Coghlan ncoghlan at gmail.com {Brisbane,澳大利亚,Gmail. com {Brisbane。

此外,还有一个未决问题(10544)似乎指向从来就不是一个好主意(PyPy, PyPy, 写在Python的Python执行文件,

底线,直到CPython的开发者告诉我们别的情况: 不要在生成器表达或理解中放出产量。

发电机中的回程声明

在Python 3 中:

在发电机函数中, 返回语句表示发电机已完成, 并将导致 StopLiveration 上升。 返回的值( 如果有的话) 用作构建 StopLiveration 的参数, 并成为 StopIturation. value 属性 。

Python 2 中的历史注释 : “ 在生成器函数中, 返回语句不允许包含表达式_ 列表 。 在此情况下, 光返回表示生成器已经完成, 并将导致停止使用 。 ” 表达式列表基本上是用逗号分隔的任何多个表达式 - 基本上在 Python 2 中, 您可以返回停止生成器, 但无法返回一个值 。

脚注脚注

将生成器的概念引入 Python 的建议中引用了语言 CLU、 Sather 和 图标 。 一般的想法是, 函数可以维持内部状态, 并产生用户需要的中间数据点 。 这承诺在性能上优于其他方法, 包括Python 线性线性, 某些系统中甚至没有这种系统。 这意味着, 范围天体虽然是可循环的, 但却不是迭代器, 因为它们是可以再利用的 。 和列表一样, 它们的 ` eter_ 方法返回替换器对象 。 收益最初是作为声明引入的, 意思是它只能在代码块的线性起始处出现 。 现在, 收益产生一种收益表达方式 。 https://docs. python.org/2/reference/spoint_stmts.html# grammar- token- yeld_stmt 。 提出这一修改是为了让用户将数据发送到生成器中。 。 要发送数据, 发送数据时, 就必须将它指定它为某种东西, 。


和每个答案一样, 收益被用于创建序列生成器。 它用于动态生成某些序列。 例如, 在按行阅读网络文件行时, 您可以使用以下的收益函数 :

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

您可在您的代码中使用以下代码:

for line in getNextLines():
    doSomeThing(line)

执行控制控制

执行控制将会从 GetNextLines () 转到执行时的循环。 因此, 每次引用 NextLines () 时, 执行都会从上次暂停处开始 。

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

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

for i in simpleYield():
    print i

将打印

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

放弃是一个对象

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

如果您想要一个函数返回一大组值,请使用收益率。

更重要的是,产量是障碍。

就像CUDA语言中的屏障, 它不会转移控制 直到它完成。

也就是说,它将从开始运行您函数中的代码,直到它产生效果。然后,它将返回循环的第一个值。

然后,其他每通电话都会运行您在函数中写下的循环, 返回下一个值, 直到没有任何值可以返回 。


产出关键字只收集返回结果。将产出想象成返回 {{}}} 。


以下是基于收益率的简单方法, 用来计算Fibonacci系列, 解释如下:

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

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

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

这是因为向 Python 发出的产出信号 表明您想要创建一个生成器, 即一个根据需求产生价值的物体。

那么,你如何生成这些值?这要么直接通过下一个使用内置函数来实现,要么间接地通过将内置函数输入一个消耗值的构造来实现。

使用下个() 内置函数, 您可以直接引用. extext/ __ extext_ , 迫使生成器产生值 :

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

间接地,如果您为循环提供纤维、列表初始化器、图普特初始化器或其他任何期望产生/产生值的对象,您将“组装”生成器,直到它不再产生(并返回):

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

类似地,图普特首发器:

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

生成器与功能不同, 因为它很懒。 它通过保持本地状态, 并允许您在需要的时候恢复运行来达到这个目的 。

当你喊叫它的时候,

f = fib()

Python 编译函数, 遇到产出关键字, 只需返回生成对象。 似乎没有什么帮助 。

当您要求它生成第一个值时, 它会直接或间接地执行它发现的所有语句, 直到它遇到一个产量, 然后它会返回您提供的产量和暂停值。 对于一个更能证明这一点的例子, 让我们使用一些打印电话( 如果在 Python 2 上用打印“ text ” 代替 打印“ 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.")

现在,输入REPL:

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

您现在有一个生成对象, 正在等待一个命令来生成一个值。 使用下一个对象并查看打印的内容 :

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

未引用的结果是打印的内容。 引用的结果是从产出中返回的内容。 现在再次调用 :

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

生成器记得它被按产出值暂停, 然后从那里恢复。 下一则消息被打印, 并搜索收益声明以在它上再次暂停( 原因是同时循环 ) 。


(我下面的回答只是从使用Python发电机的角度,而不是从发电机机制的基本实施角度,后者涉及一些堆叠和堆积操纵的伎俩。 )

当在 python 函数中使用 quot 而不是返回时, 该函数将被转换为特殊的名称 。 此函数将返回生成器类型的对象 。 产量关键字是提醒 python 编译器专门处理此函数的旗帜 。 正常函数一旦从中返回某些值就会终止 。 但是, 在编译器的帮助下, 生成器的函数可以被视为可恢复 。 也就是说, 执行环境将会恢复, 执行会从上次运行时继续 。 直到您明确调回, 这会引起一个停止引力例外( 也是循环协议的一部分) , 或者到达函数的终点 。 我发现许多关于生成器的引用, 但从功能编程角度来说, 这是一种最可消化的引用 。

(现在我想谈谈产生者背后的理由, 以及基于我自己的理解的循环器。 我希望这能帮助你掌握循环器和生成者的基本动机。 这一概念以其他语言出现, 如 C# 。 )

据我所知,当我们想要处理一大批数据时,我们通常先在某处储存数据,然后逐个处理。但这种天真的方法有问题。如果数据量很大,那么事先将数据全部储存起来费用很高。因此,与其直接储存数据本身,不如间接储存某种元数据,即数据计算逻辑。

有两种方法可以包扎这类元数据。

OO 方法, 我们把元数据包成一个类。 这是执行循环协议( 即 __ next_ () 和 __ ter_ () 方法) 的所谓迭代器。 这也是常见的迭代器设计模式 。 功能方法, 我们将元数据包成函数 。 这是所谓的生成功能 。 但是在引擎盖下, 返回的生成对象仍然是 IS - A 迭代器, 因为它也执行循环程序 。

无论哪种方式, 都会创建一个迭代器, 即某个可以提供您想要的数据的对象。 OO 处理方式可能有点复杂。 总之, 由您决定使用哪一种 。


TL; DR TR; TL; TDR

代替此:

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

这样做:

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

每当你发现自己从头开始编出一个清单时, 每一块都取而代之。

这是我第一次"啊哈"节奏节奏


收成是一种含糖的方式 说

构建一系列材料

相同行为 :

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

不同的行为 :

产量是单行道,只能绕过一次。当一个函数有收益时,我们称它为发电机功能。循环者就是它的回报。这些术语是明亮的。我们失去了一个容器的方便,但获得一系列按需要计算并任意延长的能量。

是懒惰的, 它会推卸计算。 函数中含有收益的函数在调用时不会实际执行。 它返回一个循环器对象, 记得它留下的位置。 每次您在调用循环器时( 这发生在换环) 执行步数向下一个产数前进。 返回会提高停止输出并结束序列( 这是换圈的自然结束 ) 。

产量是多功能的。数据不必全部储存在一起, 它可以一次提供一次。 它可以是无限的 。

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

如果您需要多个通行证,且系列不会太长,请在电话列表上填写:

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

英明地选择“产生”一词,因为这两个含义都适用:

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

...在系列中提供下一个数据

放弃或放弃(与政治权力一样)

...在传动器推进之前,将CPU执行。


又一个TRL;DR

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

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

您可以通过下拨“无论流量如何复杂”,将产出/生成器视为手动运行外部控制流的一种方式(如继续循环一步),然后调用该输出/生成器作为手动运行外部控制流的一种方式。

注意 : 生成器不是一个正常的函数。 它会像本地变量( stack) 一样记住先前的状态 。 请参看其他答案或文章以详细解释 。 生成器只能重复一次 。 您可以不生产, 但不会是那么好, 所以它可以被视为“ 非常好” 的语言糖 。


许多人使用回报而不是生产,但在某些情况下,收益可以更有效和更便于工作。

这里的例子绝对是收成最佳的:

返回( 在函数)

import random

def return_dates():
    dates = [] # With 'return' you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates

产出(在函数)

def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # 'yield' makes a generator automatically which works
                   # in a similar way. This is much more efficient.

呼叫功能

dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)

dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
    print(i)

两种函数都做同样的事情, 但产量使用三行而不是五行, 并且有一个更少的变量要担心。

这是代码的结果:

您可以看到两个函数都做相同的事情。 唯一的区别是 返回_ dates () 给出了列表, 而 收益_ dates () 给出了生成器 。

真实生活中的范例就是 逐行读取文件行 或者你只是想制造一个发电机


总之,产出语句将您的函数转换成一个工厂,该工厂生产一个特殊对象,称为发电机,围绕您原始函数的正文包绕。当生成器被迭代时,它将执行您的函数,直到达到下一个输出时,然后中止执行,然后对传递到的数值进行评估。它重复了每次迭代的这一过程,直到执行路径退出函数。例如,

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

for i in simple_generator():
    print i

简单产出

one
two
three

电源来自使用循环计算序列的生成器, 生成器执行循环每次停止到“ ield ” 的下一个计算结果, 这样它就可以计算飞行上的列表, 好处是存储到特别大的计算中的内存

说你想创建一个自己的范围函数, 产生一个可循环的数字范围, 你可以这样做,

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

并像这样使用它;

for i in myRangeNaive(10):
    print i

但这效率低,因为

您创建了一个只使用一次的数组( 此废物内存) 。 此代码实际上会两次循环到该数组上 ! : () : ()

幸好吉多和他的团队 慷慨地开发了发电机 这样我们就可以这么做了

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

for i in myRangeSmart(10):
    print i

在每次迭代中, 调用下一个发电机的函数执行该函数, 直至它到达“ ield” 语句停止和“ ields” 值, 或到达函数的终点。 在第一次调用的情况下, 下一个( ) 执行到产出语句, 并产生“ n ” , 下次调用它将执行递增语句, 跳回“ 此时” , 评估它, 如果是, 它会停止并再次产生“ n ” , 它会一直持续到状态返回错误, 发电机跳到函数结束的时候 。


一个容易理解它是什么的简单例子:产量

def f123():
    for _ in range(4):
        yield 1
        yield 2


for i in f123():
    print (i)

产出是:

1 2 1 2 1 2 1 2

收益率与返回率相似。区别是:

函数输出使函数可循环( 在以下示例中, 质数( n= 1) 函数成为可循环的 )。 它基本上意味着下次调用函数时, 它会从它离开的地方( 以产出表达式的线为后方) 继续 。

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

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

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

在上述例子中, 如果是inprime( n) 是真实的, 它会返回质号。 在下一个迭代中, 它会从下一行继续

n += 1  

这里所有的答案都很好,但其中只有一个(最受投票支持的)与你的代码如何运作有关。其他的与一般的发电机有关,也与它们如何运作有关。

所以,我不重复发电机是什么或产量是什么;我认为这些都包含在现有的答案中。然而,在花了几个小时试图理解一个与你的代码相似的代码之后,我将打破它是如何运作的。

您的代码绕过二进制树结构。 让我们以这棵树为例:

    5
   / \
  3   6
 / \   \
1   4   8

另一个简单的二进制搜索树的十字路口:

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

    yield self.val

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

执行代码在树形对象上,它执行__iter___这样:

def __iter__(self):

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

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

候选人发言可用树上元素替换; Python 翻译为

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

因为节点. _ iter_ 函数是一个生成器, 内部的代码按迭代执行 。 所以执行会是这样的 :

根元素是第一个; 检查它是否留下了孩子, 并且要循环它们( 因为我们叫它它 1 ) 。 它有一个孩子, 所以执行它。 给孩子自己。 左左为自己创建一个新的循环器 。 左是节点对象本身( it2) 。 左是同一逻辑 2 , 新的循环器已经创建( it3) 。 现在我们到达了树的左端 。 现在我们到达了树的左端。 它3 没有留下孩子, 所以它会继续下去并产生自我。 在下一个呼叫( it3) 时, 它会提高停止作用, 因为它没有正确的孩子( 到达函数的尽头, 但没有产生任何效果) 。 它1 和它2 仍然在活动 - 它们没有耗尽, 调用下一个( it2) 将产生值, 而不是提高停止作用 。 现在我们回到了它的上下文 2 , 并调下一个( it2) 继续它停止它的地方 : 在产生子声明之后 。 由于它没有更多的剩余孩子, 它会继续持续并产生自我 val 。 val 。

这里的渔获是,每次迭代都会产生次标准来绕过树,并保持当前迭代的状态。 一旦它到达终点,它就会绕过堆叠,并按正确的顺序返回值( 最小的收益值首先 ) 。

您的代码示例在一种不同的技术中做了类似的事情: 它为每个孩子输入了一个元素列表, 然后在下一个迭代中, 它弹出它, 并在当前对象上运行函数代码( 也就是自定义 ) 。

我希望这对这个传奇话题有一点帮助,我花了好几个小时来画这个过程来理解它。


所有的答案都是伟大的, 但对于新人来说有点困难。

我猜你已经得知回程声明了

作为类比,回归和收益是双胞胎。 回归意味着“ 回归和停止 ” , 而“ 回归”则意味着“回归,但继续 ” 。

尝试获得一份有回报的 num_ 列表 。

def num_list(n):
    for i in range(n):
        return i

运行它:

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

你看,你只得到一个数字,而不是一个他们的名单。返回永远不允许你快乐地获胜,只要执行一次就退出。

产生结果

将返回替换为产出 :

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]

现在,你赢得了所有的数字。

与一次运行和停止的返回相比, 一次运行和一次运行, 一次运行和一次运行。 您可以将返回解释为一个返回, 一次返回作为全部返回。 这叫“ 易动 ” 。

再多走一步,我们就可以重新写出回报的收益声明

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]

这是关于产量的核心。

列表返回输出与目标产出的区别是:

您总是可以从列表对象中获取 [0, 1, 2] , 但只能从“ 对象输出输出” 中提取一次 。 因此, 它有一个新的名称生成对象, 如 Out[ 11] 所示 : <generator 对象 num_ list at 0x10327c990> 。

最后,作为格罗克语的比喻:

双胞胎名单和发电机是双胞胎


在 Python 生成器( 一种特殊的迭代器) 中, 生成一系列的值, 产出关键字与 发电机功能的返回关键字相似 。

另一个令人着迷的是 生成关键词的方法 是保存生成功能的状态。

因此,我们可以设定一个数字 以不同的数值 每一次发电机产生时。

以下是一个例子:

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

def printSuccessivePrimes(iterations, base=10):
    primeGenerator = getPrimes(base)
    primeGenerator.send(None)
    for power in range(iterations):
        print(primeGenerator.send(base ** power))

就像有人要你做5个纸杯蛋糕一样。如果你做了至少一个纸杯蛋糕,你可以在做其他蛋糕时给他们吃。

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

In [5]: factory = make_cake(5)

这里称为发电机, 它会做蛋糕。 如果您叫作 Make_ 函数, 您可以得到一个发电机, 而不是运行此函数。 这是因为当输出关键字出现在一个函数中时, 它会变成一个生成器 。

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

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

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

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

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

他们消耗了所有的蛋糕, 但他们又要求一个。

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

StopIteration:

并且他们被告知不要问更多的问题。 所以一旦你消耗了发电机, 你就会用它做。 如果你想要更多的蛋糕,你需要再打电话做蛋糕。 这就像再订一份蛋糕。

In [13]: factory = make_cake(3)

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

您也可以使用上面的生成器来循环。

举个例子:假设你每次问密码时都想要随机密码。

In [22]: import random

In [23]: import string

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

In [25]: rpg = random_password_generator()

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

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

这里的 rpg 是一个生成器, 它可以生成无限数量的随机密码。 所以我们也可以说, 当我们不知道序列的长度时, 生成器是有用的, 不同于列表, 列表中有一定数量的元素 。


想象一下, 你创造了一个非凡的机器, 能够每天生成成千上万个灯泡。 机器用一个独特的序列号的盒子生成这些灯泡。 您没有足够的空间同时存储所有这些灯泡, 所以您想要调整它来生成点燃灯泡 。

Python 生成器与这个概念没有多大区别。 想象一下, 您有一个叫做条形码_ 生成器的函数, 可以为框生成独特的序列号 。 显然, 您可以在硬件( RAM) 的限制下, 由函数返回大量这样的条形码 。 一个更明智和空间效率更高的选项是按需生成这些序列号 。

机器代码 :

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


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

    # function_to_create_the_next_batch_of_lightbulbs(barcodes)

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

注意下个( 条码) 位 。

如你所见, 我们有一个自足的“ 功能” , 每次生成下一个独特的序列号。 此函数返回一个生成器 。 正如您所看到的, 我们并不是每次我们需要一个新的序列号时都会调用该功能, 而是使用下一个( ) , 给生成器来获取下一个序列号 。

低拉隔热器

更精确地说, 这个生成器是一个懒惰的循环器 。 循环器是一个帮助我们绕过一个天体序列的物体。 它被称为懒惰, 因为它在需要之前不会在内存中装入序列中的所有项目。 上一个示例中的下一个是从循环器获取下一个项目的清晰方式 。 隐含方式用于循环 :

for barcode in barcode_generator():
    print(barcode)

这将无穷尽地打印条形码, 但你不会失去内存 。

换句话说,一个发电机看起来像一个函数,但行为却像一个迭代器。

现实世界应用?

最后, 真实世界应用程序 。 当您在大序列中工作时, 它们通常是有用的 。 想象一下从有数十亿记录的磁盘上读取一个巨大的文件。 在您能够处理其内容之前, 在记忆中读取整个文件, 很可能是行不通的( 也就是说, 您将失去记忆 ) 。


python 的输出与返回语句类似,但有些差异除外。如果要从函数返回多个值,返回语句将把所有值都作为列表返回,并将其存储在调用符块的内存中。但如果我们不想使用额外的内存,会怎样?相反,我们需要在需要时从函数中获取该值。这是产出的来源。考虑以下函数:

def fun():
   yield 1
   yield 2
   yield 3

打电话的人是:

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

上述代码段(调用函数),如果调用,产出:-

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

从上文可以看出, 产出返回其调用器的值, 但当函数再次调用时, 它不会从第一个语句开始, 而是从产出后右侧的语句开始。 在上述示例中, “ 第一值打印” 打印, 函数被调用。 1 被回传并打印。 然后, 打印“ 第二值打印” , 并再次调用有趣 () 。 它不打印 1 (第一个语句) , 而是返回 2 , 也就是说, 仅从产出 1 之后的语句 。 同样的程序会进一步重复 。


也可以将数据发送回生成器!

事实上,正如这里许多答案所解释的那样,利用产量产生一个发电机。

您可以使用产出关键字将数据发送回“实时”生成器。

示例:

假设我们有一种方法可以从英语翻译成其他语言。 在开始的时候, 它会做一些很重的事情, 应该做一次。 我们希望这个方法可以永远运行( 不知道为什么..... . :) , 并且收到要翻译的单词 。

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

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

运行中 :

my_words_translator = translator()

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

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

将打印 :

dog in other language
Unknown word...

概括如下:

使用发件人内部发送方法将数据发送回发件人。要允许,使用 a (ield) 。


- 功能 - 返回。

发电机 -- -- 产量(含有一个或多个产量和零或更多回报率)。

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


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

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


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

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

发电机看起来像一个函数,但行为举止却像一个迭代器。

发件人继续从它所在的位置执行 。 恢复后, 函数在最后产值运行后立即继续执行 。 这允许它的代码在一段时间内生成一系列的值, 代之以它们一次性计算全部值, 然后把它们像列表一样送回去 。

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

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

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

注:放弃不应在尝试中.最终建造。


通常情况下, 它会用来创建一个不起作用的代名词。 将“ ield” 当作您函数的附加件, 以及您作为数组的函数。 如果符合某些标准, 您可以在函数中添加此值, 使之成为代名词 。

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

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

两者的输出结果相同。

使用产量的主要优势是创建迭代器。 迭代器在即时计算时不会计算每个项目的价值。 它们只在您要求时才计算。 这被称为懒惰评价 。


简单解答

当函数包含至少一个输出语句时,函数自动成为生成函数。当您调用生成函数时, python 在生成函数中执行代码,直到生成语句发生。 当您调用生成函数时, python 继续从冻结位置执行生成函数中的代码,直到生成语句发生反复发生。 生成函数执行代码,直到生成函数在没有生成语句的情况下运行结束。

基准基准基准基准基准基准基准

创建列表并返回它 :

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

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

function()

结果有:

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

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



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

在飞行上生成值 :

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

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

function()

结果有:

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

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



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

摘要摘要摘要

生成器函数需要稍多一点时间来执行, 而不是返回列表但少用内存的函数 。


简单使用实例 :

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

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

100
20
3
>>> 

如何运行 : 调用时, 函数会立即返回对象。 对象可以传递到下一个( ) 函数 。 当调用下一个( ) 函数时, 您的函数会一直运行到下一个产值, 并为下一个( ) 函数提供返回值 。

在引擎盖下, 循环确认对象是一个生成对象, 并使用下一个( ) 来获取下一个值 。

在一些语言中,比如ES6和更高语言中,它的实施略有不同, 所以下一个是生成对象的成员函数, 每次它得到下一个值时, 你就可以从调用器中传递数值。 所以如果结果是生成器, 那么你可以做类似y=结果。 ext( 555) , 而程序生成值可以说像 z = 产值 999 。 y 的值将是 999 , 下一个产值是 999, 而 z 的值将是 555 , 下一个产值是 555。 Python 获取并发送方法也有类似的效果 。


发电机可以使个别经过处理的物品立即得到处理(不必等待整个收集过程的处理),下面的例子说明了这一点。

import time

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

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


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

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

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

get_list iteration (results come all at once)
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds
result arrived after: 10 seconds

要理解它的产值函数,人们必须理解发电机是什么。此外,在理解发电机之前,你必须理解易用的东西。易用 : 易用 : 要创建列表, 您自然需要能够逐个读取每个元素。 逐项读取其项目的过程被称为迭代 :

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

My list 是可替换的。 当您使用列表理解值时, 您会创建一个列表, 因此该列表是可替换的 :

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

所有可用于... 的数据结构都是可循环的; 列表、 字符串、 文件...

这些惯用方法很方便,因为您可以随意阅读,但您可以将所有值存储在记忆中,当您有许多值时,这些值并不总是可取的。 生成器: 生成器 A 也是一种迭代器, 一种特殊的迭代器, 只能迭代一次。 生成器不会将所有值存储在记忆中, 而是在苍蝇上生成值 :

发电机:发电机、发电机、发电机发电,但不储存能源;)

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

只要使用 () 而不是 [] , 列表理解就会变成发电机理解。 但是, 由于发电机只能使用一次, 您无法在我的生成器中执行 i 第二次 : 生成器计算 0, 然后丢弃它, 然后计算 1, 最后一次计算 4 。 典型的黑色盲人打破玉米 。

产出关键字的使用方式与返回相同,但函数返回生成器。

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

这个例子本身是毫无用处的,但是当您需要函数返回大量数值,而只需要读一次,使用产量就方便了。

要掌握收益率,需要清楚的是,当函数被调用时,函数正文中写入的代码将不会运行。函数只返回生成对象。启动者可能会对此感到困惑。

第二,明白代码会从每次使用发电机时留下的代码中继续使用。

现在最困难的部分是:

第一次调用您函数所创建的生成器对象时, 它会运行函数中的代码, 从开始一直运行到产生, 然后返回循环的第一个值。 然后, 以后的每次调用都会运行您在函数中写入的循环的下一个迭代, 并返回下一个值。 这将一直持续到生成器被视为空, 当函数运行时没有被击中时该生成。 这可能是因为循环已经结束, 或者因为您不再满足于“ if/ else ” 。

个人理解 我希望帮助你!


Python 中的输出关键字用于退出代码,而不会扰乱本地变量的状况,当函数再次被称作“执行”时,从我们离开代码的最后一点开始。

以下示例显示了产量的作用:

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

for y in counter():
    print(y)

上述代码产生以下输出:

Initial value of x:  <generator object counter at 0x7f0263020ac0>
2
3
4

生成关键字用于查点/字符,其中函数预期将返回一个输出。我想引用这个非常简单的例A:

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

以上函数只返回一次, 即使它被多次调用。 现在如果我们以收益率替换返回, 如例B :

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

当第一次叫2时,它会返回1,当再次叫2时,3,4,然后它会递增到10。

虽然B的例子在概念上是真实的,但要用Python 3来称呼它,我们必须采取以下行动:


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

# so to assign it to a variables
v = getNumber()
v1 = next(v) #v1 will have 1
v2 = next(v) #v2 will have 2
v3 = next(v) #v3 will have 3

关键要点

Python 的语法库使用输出关键字的存在来设定返回生成器的函数。 生成器是一种迭代器, 这是一种在 Python 中循环的主要方式。 生成器本质上是一个可回收的函数。 与返回返回值和结束函数不同的是, 生成关键字返回一个值和中止函数。 当下一个( g) 被调用到生成器时, 函数会恢复它所剩的功能。 只有当函数遇到一个明示或暗示返回时, 它才会实际结束 。

书写和理解发电机技术

一个理解和思考发电机的简单方法就是用印刷品()而不是产量来撰写一个常规函数:

def f(n):
    for x in range(n):
        print(x)
        print(x * 10)

注意它的产出:

>>> f(3)
0
0
1
10
2
2

3⁄4 ̄ ̧漯B

def f(n):
    for x in range(n):
        yield x
        yield x * 10

给 :

>>> list(f(3))
[0, 0, 1, 10, 2, 20]

迭代程序协议

答案“什么产量能做什么”可以是简短和简单的, 但是它是更大的世界的一部分, 所谓的“标准协议”。

在迭代协议的发送方,有两种相关的对象。可循环的物体是您可以绕过的东西。迭代器是跟踪环状状态的物体。

在迭代协议的消费方方面,我们调用可迭代对象的迭代者获得一个迭代者。然后我们调用下一个迭代者从迭代器中获取值。当没有更多数据时,会提出一个停止使用例外 :

>>> s = [10, 20, 30]    # The list is the "iterable"
>>> it = iter(s)        # This is the "iterator"
>>> next(it)            # Gets values out of an iterator
10
>>> next(it)
20
>>> next(it)
30
>>> next(it)
Traceback (most recent call last):
 ...
StopIteration

为了让这一切变得更容易, 对于卢布人来说,叫它, 下一个代表我们:

>>> for x in s:
...     print(x)
...   
10
20
30

一个人可以写一本关于这一切的书, 但这些都是关键点。 当我教授 Python 课程时, 我发现这是一个最起码的足够解释 来建立理解, 并马上开始使用它。 特别是, 用打印写一个函数, 测试它, 然后转换成收益的技巧, 似乎对 Python 各级程序员都有效 。


用于创建生成器 。 将生成器想象成一个迭代器 highc , 给您每个迭代值 。 当您在循环中使用 收益率 时, 就会得到一个生成器对象, 您可以用该对象从循环中以迭接方式从循环中获取项目 。


佩顿有什么差错?

Python 中的 Yield 关键字类似于用于返回 Python 中的值或对象的返回语句。 但是, 存在微小的差别。 收益语句返回一个生成符, 而不是简单地返回一个值, 而返回一个函数的生成符。

在程序内,当您调用一个函数,该函数有一个输出语句时,一旦遇到一个输出,函数的执行即停止,然后将生成器的一个对象返回到函数调用器。用更简单的文字,产出关键字将把一个与该关键字一起指定的表达式转换为生成器对象,然后返回到调用器。因此,如果您想要获得在生成器对象内存储的值,则需要将该关键字复制到该对象上。

它不会破坏本地变量的状态。 当调用函数时, 执行将从最后一个输出表达式开始。 请注意, 包含输出关键字的函数被称为生成函数 。

当您使用含有返回值的函数时,每次调用函数时,该函数从一组新的变量开始。反之,如果使用一个生成函数而不是正常函数,则执行将从它左最后的位置开始。

如果您想要从函数中返回多个值, 您可以使用输出关键字来使用生成函数。 输出表达式返回多个值。 它们返回一个值, 然后等待, 保存本地状态, 然后再恢复 。

资料来源:https://www.simplilearn.com/tutorics/python-tutoric/yield-in-python。


输出允许您通过将循环部分乘以一个便于再利用的单独方法来写出更聪明的编剧。

假设你需要环绕电子表格的所有非空白行,对每行都做一些事情。

for i, row in df.iterrows(): #from the panda package for reading excel 
  if row = blank: # pseudo code, check if row is non-blank...
    continue
  if past_last_row: # pseudo code, check for end of input data
    break
  #### above is boring stuff, below is what we actually want to do with the data ###
  f(row)

如果您在类似循环中需要调用 g( row) , 您可能会发现自己重复了对数, 并重复了对数的检查, 有效行的检查是无聊、 复杂和容易出错的。 我们不想重复( DRY 原则 ) 。

您想要将检查每个记录的代码与实际处理行的代码区分开来, 例如 f( row) 和 g( row) 。

您可以设定一个函数, 将 f () 作为输入参数, 但是在一种方法中使用收益率要简单得多, 这种方法可以做所有关于检查有效行的无聊事情, 准备拨打 f () :

def valid_rows():
  for i, row in df.iterrows(): # iterate over each row of spreadsheet
    if row == blank: # pseudo code, check if row is non-blank...
      continue
    if past_last_row: # pseudo code, check for end of input data
      break
    yield i, row

请注意,该方法的每次调用都会返回下一行, 但如果所有行都读取, 并用于结束部分, 方法会正常返回。 下一次调用将开始新的循环 。

现在您可以在数据上写入迭代, 而不必重复对有效行进行无趣的检查( 现在根据自己的方法来计算) , 例如 :

for i, row in valid_rows():
  f(row)

for i, row in valid_rows():
  g(row)

nr_valid_rows = len(list(valid_rows()))

仅此而已。 请注意, 我还没有使用诸如 迭代器、 生成器、 协议、 共同常规等术语 。 我认为这个简单的例子 适用于我们日常的许多编码 。


产出 :

可以通过停止函数从函数返回一个值的多次。 您可以从它中返回一个值, 如从中产生 。 当返回大数据时, 将它分成小部分数据, 以防止大量使用内存 。

例如,下面的测试 () 可以通过停止测试( ) 逐个返回“ 1 ” 、 “ 2 ” 和 [ “ 3 ” 、 “ 四 ” 。 因此, 测试( ) 总共返回3倍, 总共返回3倍, 停止测试( ) 共返回3倍 :

def test():
    yield 'One'                  # Stop, return 'One' and resume 
    yield 'Two'                  # Stop, return 'Two' and resume
    yield from ['Three', 'Four'] # Stop and return ['Three', 'Four'] 

下面这三套代码可以调用测试() 并打印“ 1 ” 、 “ 2 ” 、 “ 三 ” 和 “ 四 ” :

for x in test():
    print(x)
x = test()
print(next(x))
print(next(x))
print(next(x))
print(next(x))
x = test()
print(x.__next__())
print(x.__next__())
print(x.__next__())
print(x.__next__())

其结果是:

$ python yield_test.py
One
Two
Three
Four

此外,在利用回报和产出时,没有办法从回报中获得价值:

def test():
    yield 'One' 
    yield 'Two'
    yield from ['Three', 'Four']
    return 'Five' # 'Five' cannot be got

x = test()
print(next(x))
print(next(x))
print(next(x))
print(next(x))
print(next(x)) # Here

因此,在试图获取“ 五” 时, 下面有一个错误 :

$ python yield_test.py 
One
Two
Three
Four
Traceback (most recent call last):
  File "C:\Users\kai\yield_test.py", line 12, in <module>
    print(next(x))
          ^^^^^^^
StopIteration: Five