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 的语法库使用输出关键字的存在来设定返回生成器的函数。 生成器是一种迭代器, 这是一种在 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 各级程序员都有效 。

其他回答

理解产出的快捷键

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

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

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

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

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

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

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

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发电机的实际安装则更为复杂。 但是,为了了解正在发生的事情,试图使用持续的传记风格来实施生成器,而不使用产出关键字。

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

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

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

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

这是如何重写产量的演示, 它使用 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 - >

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

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

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

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

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)

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

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

现实世界应用?

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