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链接。


当前回答

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

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)

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

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

现实世界应用?

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

其他回答

理解产出的快捷键

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

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

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

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

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

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 ) 方法中执行下一个逻辑步骤 。 根据逻辑, 下一个( ) 方法中的代码可能最终会查找非常复杂和易被错误的代码 。 在这里, 生成器可以提供一个简单明了的解决方案 。

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

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"

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

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

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

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

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

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

比较以下实例:

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 (.) 来探索它们, 我们可以确认上面的心理模型是可信的 。

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

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

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

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

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

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

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

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

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

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