Python中yield
关键字的用途是什么? 它有什么做用? html
例如,我试图理解这段代码1 : node
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
这是呼叫者: python
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_candidates
方法时会发生什么? 是否返回列表? 一个元素? 再叫一次吗? 后续通话什么时候中止? 程序员
1.这段代码是由Jochen Schulz(jrschulz)编写的,Jochen Schulz是一个很好的用于度量空间的Python库。 这是完整源代码的连接: Module mspace 。 编程
如下是一些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)
使用对象闭包而不是生成器 (由于ClosuresAndObjectsAreEquivalent ) 函数
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)
产量能够为您提供发电机。 ui
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万个列表,该怎么办? 这不只是一个巨大的内存消耗者,并且在调用该函数时还花费大量时间来构建。
在第二种状况下, bar
只是为您提供了一个生成器。 生成器是可迭代的-这意味着您能够在for
循环等中使用它,可是每一个值只能被访问一次。 全部的值也不会同时存储在存储器中。 生成器对象“记住”您上次调用它时在循环中的位置-这样,若是您使用的是一个迭代的(例如)计数为500亿,则没必要计数为500亿当即存储500亿个数字以进行计算。
再次,这是一个很是人为的示例,若是您真的想计数到500亿,则可能会使用itertools。 :)
这是生成器最简单的用例。 如您所说,它能够用来编写有效的排列,使用yield能够将内容推入调用堆栈,而不是使用某种堆栈变量。 生成器还能够用于特殊的树遍历以及全部其余方式。
对于那些偏心简单工做示例的人,请在此交互式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页,以快速了解生成器”,可是已经有许多其余人发布了不错的描述。
另外,请注意,协程能够将yield
用做生成函数的双重用途。 尽管(yield)
与代码段用法不一样,但它能够用做函数中的表达式。 当调用者使用send()
方法向该方法发送值时,协程将一直执行,直到遇到下一个(yield)
语句为止。
生成器和协程是设置数据流类型应用程序的一种很酷的方法。 我认为值得了解函数中yield
语句的其余用法。
在描述如何使用生成器的许多很棒的答案中,我尚未给出一种答案。 这是编程语言理论的答案:
Python中的yield
语句返回一个生成器。 (and specifically a type of coroutine, but continuations represent the more general mechanism to understand what is going on). Python中的生成器是一个返回的函数(特别是协程类型,可是延续表明了一种更通用的机制来了解正在发生的事情)。
编程语言理论中的连续性是一种更为基础的计算,可是因为它们很难推理并且也很难实现,所以并不常用。 可是,关于延续是什么的想法很简单:只是还没有完成的计算状态。 在此状态下,将保存变量的当前值,还没有执行的操做等。 而后,在稍后的某个时刻,能够在程序中调用继续,以便将程序的变量重置为该状态,并执行保存的操做。
以这种更通常的形式进行的延续能够两种方式实现。 以call/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编程中常用这种设计模式,这是由于它节省了个人代码行,或更重要的是,在GUI事件触发后管理了控制流。)
在不失通常性的前提下,本文的其他部分将连续性概念化为CPS,由于它很容易理解和阅读。
如今让咱们谈谈Python中的生成器。 生成器是延续的特定子类型。 延续一般可以保存计算状态 (即程序的调用堆栈),而生成器只能保存迭代器上的迭代状态 。 虽然,对于发电机的某些用例,此定义有些误导。 例如:
def f(): while True: yield 4
显然,这是一个合理的迭代器,其行为已获得很好的定义-每次生成器对其进行迭代时,它都会返回4(并永远这样作)。 可是在考虑迭代器时(例如, for x in collection: do_something(x)
),可能不会想到原型的可迭代类型。 此示例说明了生成器的功能:若是有什么是迭代器,生成器能够保存其迭代状态。
重申一下:连续能够保存程序堆栈的状态,而生成器能够保存迭代的状态。 这意味着延续比生成器强大得多,可是生成器也很是简单。 它们对于语言设计者来讲更容易实现,对程序员来讲也更容易使用(若是您有时间要燃烧,请尝试阅读并理解有关延续和call / cc的本页 )。
可是您能够轻松地将生成器实现(并概念化)为连续传递样式的一种简单的特定状况:
每当调用yield
,它都会告诉函数返回一个延续。 再次调用该函数时,将从中断处开始。 所以,在伪伪代码(即不是伪代码,而不是代码)中,生成器的next
方法基本上以下:
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
其中yield
关键字其实是实际生成器函数的语法糖,基本上是这样的:
def generatorfun(iterable): if len(iterable) == 0: raise StopIteration else: return (iterable[0], lambda:generatorfun(iterable[1:]))
请记住,这只是伪代码,Python中生成器的实际实现更为复杂。 可是,做为练习以了解发生了什么,请尝试使用连续传递样式来实现生成器对象,而不使用yield
关键字。