例如下面这段代码:

    下面是调用它:

    1. while candidates:
    2. node = candidates.pop()
    3. distance = node._get_dist(obj)
    4. if distance <= max_dist and distance >= min_dist:
    5. result.extend(node._values)
    6. candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
    7. return result

    _get_child_candidates方法被调用的时候发生了什么?是返回一个列表?还是一个元祖?它还能第二次调用吗?后面的调用什么时候结束?


    为了理解yield有什么用,首先得理解generators,而理解generators前还要理解iterables

    Iterables

    当你创建了一个列表,你可以一个一个的读取它的每一项,这叫做iteration:

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

    Mylist是可迭代的.当你用列表推导式的时候,你就创建了一个列表,而这个列表也是可迭代的:

    所有你可以用在for…in…语句中的都是可迭代的:比如lists,strings,files…因为这些可迭代的对象你可以随意的读取所以非常方便易用,但是你必须把它们的值放到内存里,当它们有很多值时就会消耗太多的内存.

    Generators

    生成器也是迭代器的一种,但是你只能迭代它们一次.原因很简单,因为它们不是全部存在内存里,它们只在要调用的时候在内存里生成:

    1. >>> mygenerator = (x*x for x in range(3))
    2. >>> for i in mygenerator:
    3. ... print(i)
    4. 0
    5. 1
    6. 4

    生成器和迭代器的区别就是用()代替[],还有你不能用for i in mygenerator第二次调用生成器:首先计算0,然后会在内存里丢掉0去计算1,直到计算完4.

    Yield

    1. >>> def createGenerator():
    2. ... mylist = range(3)
    3. ... for i in mylist:
    4. ... yield i*i
    5. >>> mygenerator = createGenerator() # 创建生成器
    6. >>> print(mygenerator) # mygenerator is an object!
    7. >>> for i in mygenerator:
    8. ... print(i)
    9. 0
    10. 1
    11. 4

    在这里这个例子好像没什么用,不过当你的函数要返回一个非常大的集合并且你希望只读一次的话,那么它就非常的方便了.

    要理解Yield你必须先理解当你调用函数的时候,函数里的代码并没有运行.函数仅仅返回生成器对象,这就是它最微妙的地方:-)

    然后呢,每当for语句迭代生成器的时候你的代码才会运转.

    现在,到了最难的部分:

    for语句第一次调用函数里返回的生成器对象,函数里的代码就开始运作,直到碰到yield,然后会返回本次循环的第一个返回值.所以下一次调用也将运行一次循环然后返回下一个值,直到没有值可以返回.

    一旦函数运行并没有碰到yeild语句就认为生成器已经为空了.原因有可能是循环结束或者没有满足if/else之类的.

    对于你的代码的解释

    生成器:

    调用:

    1. # 创建空列表和一个当前对象索引的列表
    2. result, candidates = list(), [self]
    3. # 在candidates上进行循环(在开始只保含一个元素)
    4. while candidates:
    5. # 获得最后一个condidate然后从列表里删除
    6. node = candidates.pop()
    7. # 获取obj和candidate的distance
    8. distance = node._get_dist(obj)
    9. # 如果distance何时将会填入result
    10. if distance <= max_dist and distance >= min_dist:
    11. result.extend(node._values)
    12. candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
    13. return result

    这段代码有几个有意思的地方:

    • extend()是一个列表对象的方法,它可以把一个迭代对象添加进列表.

    我们经常这么用:

    1. >>> a = [1, 2]
    2. >>> b = [3, 4]
    3. [1, 2, 3, 4]

    但是在你给的代码里得到的是生成器,这样做的好处:

    • 你不需要读这个值两次
    • 你能得到许多孩子节点但是你不希望他们全部存入内存.
      这种方法之所以能很好的运行是因为Python不关心方法的参数是不是一个列表.它只希望接受一个迭代器,所以不管是strings,lists,tuples或者generators都可以!这种方法叫做duck typing,这也是Python看起来特别cool的原因之一.但是这又是另外一个传说了,另一个问题~~

    好了,看到这里可以打住了,下面让我们看看生成器的高级用法:

    控制迭代器的穷尽

    它对于一些不断变化的值很有用,像控制你资源的访问.

    Itertools,你的好基友

    itertools模块包含了一些特殊的函数可以操作可迭代对象.有没有想过复制一个生成器?链接两个生成器?把嵌套列表里的值组织成一个列表?Map/Zip还不用创建另一个列表?

    来吧import itertools

    来一个例子?让我们看看4匹马比赛有多少个排名结果:

    1. >>> horses = [1, 2, 3, 4]
    2. >>> races = itertools.permutations(horses)
    3. >>> print(races)
    4. <itertools.permutations object at 0xb754f1dc>
    5. >>> print(list(itertools.permutations(horses)))
    6. [(1, 2, 3, 4),
    7. (1, 2, 4, 3),
    8. (1, 3, 2, 4),
    9. (1, 3, 4, 2),
    10. (1, 4, 2, 3),
    11. (1, 4, 3, 2),
    12. (2, 1, 3, 4),
    13. (2, 1, 4, 3),
    14. (2, 3, 1, 4),
    15. (2, 3, 4, 1),
    16. (2, 4, 1, 3),
    17. (2, 4, 3, 1),
    18. (3, 1, 2, 4),
    19. (3, 1, 4, 2),
    20. (3, 2, 1, 4),
    21. (3, 2, 4, 1),
    22. (3, 4, 1, 2),
    23. (3, 4, 2, 1),
    24. (4, 1, 2, 3),
    25. (4, 1, 3, 2),
    26. (4, 2, 1, 3),
    27. (4, 2, 3, 1),
    28. (4, 3, 2, 1)]

    理解迭代的内部机制

    迭代是可迭代对象(对应iter()方法)和迭代器(对应next()方法)的一个过程.可迭代对象就是任何你可以迭代的对象(废话啊).迭代器就是可以让你迭代可迭代对象的对象(有点绕口,意思就是这个意思)