[stackoverflow翻译] “yield” 关键字有什么用?what-does-the-yield-keyword-do

来源:互联网 发布:开淘宝店前期如何推广 编辑:程序博客网 时间:2024/05/29 13:23
问:

在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 = list(), [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 被调用时发生了什么? 返回了一个list吗?还是单一的一个元素? 会再一次调用吗? 后续的调用何时停止?


1. 这段代码来自Jochen Schulz (jrschulz), 他创建了一个优秀的关于度量空间的Python库(原文为:a great Python library for metric spaces). 来源链接 Module mspace.


答:

为了理解yield 是什么, 你必须理解生成器是什么. 在理解生成器之前,你需要理解可迭代对象(iterables).

可迭代对象

当你创建一个列表时,你可以逐个地读他的元素.这样一个一个地读取他的元素就叫做迭代:

>>> mylist = [1, 2, 3]>>> for i in mylist:...    print(i)123

mylist 是一个可迭代对象. 当你创建一个列表时,也就创建了一个可迭代对象:

>>> mylist = [x*x for x in range(3)]>>> for i in mylist:...    print(i)014

任何你能使用"for... in..."语句来操作的都是可迭代对象;比如 列表字符串, 文件,等等. 

这些可迭代对象是便利的,你可以按意愿来读取他们.但这时你就存了所有的值在内存中,当值的数量非常大时,这就不会是你想要的了. 

生成器(Generators)

生成器是可迭代对象,但你只能迭代他们一次.这是因为它们不在内存中存储所有的值,它们匆忙地生成值(they generate the values on the fly):

>>> mygenerator = (x*x for x in range(3))>>> for i in mygenerator:...    print(i)014

你用() 替代 []的结果是一样的,但是你不能二次执行 for i in mygenerator 因为生成器只能被使用一次: 

计算得出0,计算得出1,计算得出4,结束.

Yield

Yield 是一个可以像 return一样使用的关键字,不同之处在于函数将返回一个生成器.

>>> def createGenerator():...    mylist = range(3)...    for i in mylist:...        yield i*i...>>> mygenerator = createGenerator() # 创建一个生成器>>> print(mygenerator) # mygenerator 是一个对象!<generator object createGenerator at 0xb7555c34>>>> for i in mygenerator:...     print(i)014

这是一个无用的例子,但当你知道你的函数将返回一个巨大的包含值的集合且你只需要读一次时,他将会很方便. 

为了精通 yield, 你必须知晓:当你调用函数时,你写在函数体内的代码并没有执行.这个函数仅仅返回一个生成器对象,这有点需要技巧(this is a bit tricky :-))随后,你的代码将在 for 操作生成器的时候执行 .


下面难的来了:

当第一次 for 调用生成器对象时,他将运行你写在函数体里的代码,直到他遇到 yield, 然后返回循环的第一个值. 再然后, 每次调用将再次运行循环, 并返回下一个值, 直到没有值可以返回.

当函数运行时,倘若不再遇到 yield 关键字,这个生成器就被认为是空的. 这可能是因为循环已经终止或者不再满足任何一个  "if/else" .


你代码的解释

生成器:

# 此处你创建了node对象的一个方法并将返回生成器def node._get_child_candidates(self, distance, min_dist, max_dist):  # Here is the code that will be called each time you use the generator object:  # 如果 node 对象具有左子节点  # 并且距离也是ok的, 那就返回这个左子节点  if self._leftchild and distance - max_dist < self._median:      yield self._leftchild  # 如果 node 对象具有有子节点  # 并且距离也是ok的, 那就返回这个右子节点  if self._rightchild and distance + max_dist >= self._median:      yield self._rightchild  # 如何函数运行到了这里, 这个生成器将被认为是空的  # 再没有左右子节点这样两个值

调用:

# 创建一个空的列表,和一个当前对象的引用(Create an empty list and a list with the current object reference)result, candidates = list(), [self]# 在候选值列表(cndidates)中循环 (在开始时只包含一个元素)while candidates:    # 获取候选列表中的最后一个值并将其从列表中移除    node = candidates.pop()    # 获取 obj 和 candidate 之间的距离    distance = node._get_dist(obj)    # 如果距离是ok的, 就填入结果列表(result)中    if distance <= max_dist and distance >= min_dist:        result.extend(node._values)    # 在候选值列表(candidates)中加入候选值的子节点    # 循环将继续运行,直到 until it will have looked    # 候选值的子节点的子节点的子节点......    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))return result

这段代码包含了几个聪明的部分:

  • 这个循环迭代一个列表但是这个列表在被迭代后将会扩展 :-) 这是一个简明的方法去遍历所有内嵌的数据,即使这样有一点危险会导致无限循环下去. 在这种情况下, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) 穷尽生成器的所有值, 但 while 循环keeps creating new generator objects which will produce different values from the previous ones since it's not applied on the same node.

  • The extend() 方法是一个列表对象方法,它接收一个可迭代对象并将他的值添加到列表中.

Usually we pass a list to it:

>>> a = [1, 2]>>> b = [3, 4]>>> a.extend(b)>>> print(a)[1, 2, 3, 4]

But in your code it gets a generator, which is good because:

  1. You don't need to read the values twice.
  2. You may have a lot of children and you don't want them all stored in memory.

And it works because Python does not care if the argument of a method is a list or not. Python expects iterables so it will work with strings, lists, tuples and generators! This is called duck typing and is one of the reason why Python is so cool. But this is another story, for another question...



问题链接:http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do

个人水平有限,如有不当之处,敬请谅解.

0 0
原创粉丝点击