原文地址:what is python keyword yield used for?
1.引入
首先,以一个例子开头,如何解释下面的代码:
def node._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?还是返回了一个列表的元素?它被重复调用了?接下来的调用什么时候结束?
在了解yield之前,我们需要了解generator,在了解generator之前,又必须了解iterables。
2.Iterables——可迭代
当你创建一个列表的时候,你可以一个一个地读取他的元素。一个个读取元素的过程就叫做迭代。
例如:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist就ishi可迭代的。当你使用一个列表解析器创建列表的时候,你创建的列表就是可迭代的。
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
对于一个对象,如果你能对它使用for … in …表达式,那么它就是可迭代的,例如:lists,string,files等等等。
这些可迭代的的东东使用起来特别方便,你可以任意地读取它们。但是,它们中的值都也是存储在内存中的,也就意味着当你有很多值需要存储的时候,你就会遇到麻烦(内存不够)。
3.Generators-生成器
生成器也是迭代器的一种,但是,你仅仅可以迭代使用它们一次。因为它们不会将迭代到的所有数据存储在内存中,这些值是动态生成的,一直在变化。
例如;
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
除了你使用()而不是[]之外和迭代器没有其他区别。但是你不可以这样——for i in mygenerator使用它第二次,因为generator只可以被使用一次;先计算0,然后忘记它,然后计算1,然后再忘记它,再计算…一个一个直到所有元素被迭代玩。
4.Yield
Yield是个类似return的关键字,不过它返回一个generator。
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
这是一个无用的例子,但是当你知道你需要返回的是一个只需要读取一次的巨大的数据集的时候,这种方式是非常好的。
为了掌握yield关键字,你必须知道当你调用函数的时候,写在函数体中的代码并不会运行。函数只返回generator对象,这里有点难懂。
然后,每次使用generator的时候,都会去运行一次函数体createGenerator()里面的代码。
现在我们讨论最难的部分:
第一次调用我们函数创建的generator的时候,会从头运行你函数中的代码直到遇见yield关键词,然后它会返回循环的第一个值。接下来的每次调用会再次执行你函数中的循环,并且返回下一个值,直到没有值返回为止。
在没有执行到yield关键词时,generator都是空值。或者是循环没结束,再者就是你没有满足if/else。
5.最上面代码的解释
此处,你创建了node对象的一个返回generator的方法:
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:
# If there is still a child of the node object on its left
# AND if distance is ok, return the next child
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
# If there is still a child of the node object on its right
# AND if distance is ok, return the next child
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
# If the function arrives here, the generator will be considered empty
# 函数执行到此处,那么generator就是空的
# there is no more than two values: the left and the right children
# 无非就两种值:左右字节点
Caller:# Create an empty list and a list with the current object reference
调用者:创建一个空的列表和一个指向当前对象引用的列表
result, candidates = list(), [self]# Loop on candidates (they contain only one element at the beginning)
while candidates:
# Get the last candidate and remove it from the list
node = candidates.pop()
# Get the distance between obj and the candidate
distance = node._get_dist(obj)
# If distance is ok, then you can fill the result
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
# Add the children of the candidate in the candidates list
# so the loop will keep running until it will have looked
# at all the children of the children of the children, etc. of the candidate
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
这段代码的做法十分聪明:
循环在不断扩展的list上迭代。这是一种简单的方式遍历嵌套的数据,即使会有一点风险——那就是可能会死循环。在此例中,candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))穷尽了generator的所有值,同时也在不断地创建新的generator对象,这些generator对象会依赖上一个节点生成不同的值,因为它不会依赖同一个节点。
extend()方法是list对象的方法,它的参数是一个iterable·对象,并且会将它的值添加进list中。
通常,我们会传递一个list对象给它。
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
但是在你的代码中,你传给它的是一个generator,这样做也是OK的,因为:
你不需要重复读取它的值;
其次是你可以有非常多的字节点,然而你不必将它们存进内存。
上面的代码能够工作的原因是python不必管函数的参数是否是一个list或者其他的什么东东。Python要的是iterables,所以参数可以是strings,lists,tuples,generators等等!这叫做动态类型(duck typing)这也是为什么python这么酷的原因。