Python中yield
关键字的用途是什么? 它有什么作用?
例如,我试图理解这段代码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_candidates
方法时会发生什么? 是否返回列表? 一个元素? 再叫一次吗? 后续通话何时停止?
1.这段代码是由Jochen Schulz(jrschulz)编写的,Jochen Schulz是一个很好的用于度量空间的Python库。这是完整源代码的链接: Module mspace 。
#1楼
以下是一些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)
#2楼
产量可以为您提供发电机。
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可以将内容推入调用堆栈,而不是使用某种堆栈变量。 生成器还可以用于特殊的树遍历以及所有其他方式。
#3楼
对于那些偏爱简单工作示例的人,请在此交互式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
#4楼
我本打算发布“阅读Beazley的“ Python:基本参考”的第19页,以快速了解生成器”,但是已经有许多其他人发布了不错的描述。
另外,请注意,协程可以将yield
用作生成函数的双重用途。 尽管(yield)
与代码段用法不同,但它可以用作函数中的表达式。 当调用者使用send()
方法向该方法发送值时,协程将一直执行,直到遇到下一个(yield)
语句为止。
生成器和协程是设置数据流类型应用程序的一种很酷的方法。 我认为值得了解函数中yield
语句的其他用法。
#5楼
在描述如何使用生成器的许多很棒的答案中,我还没有给出一种答案。 这是编程语言理论的答案:
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
关键字。
#6楼
解决方案一: “ yield”关键字有什么作用?解决方案二: What does the “yield” keyword do?
来源:oschina
链接:https://my.oschina.net/u/3797416/blog/3208224