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 的发电机功能返回了连续性( 具体地说, 是一种共同的常规, 但连续性代表了了解情况的一般机制 ) 。

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

以这种更一般的形式, 延续可以用两种方式执行 。 以调用/ 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发电机的实际安装则更为复杂。 但是,为了了解正在发生的事情,试图使用持续的传记风格来实施生成器,而不使用产出关键字。

其他回答

关键要点

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 各级程序员都有效 。

我不太熟悉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

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

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

以下是基于收益率的简单方法, 用来计算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!'

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

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

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

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

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

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