python中的yield关键字

作者: 大蟒传奇 | 来源:发表于2016-09-16 23:39 被阅读98次
    Yield

    基础概念

    可迭代对象

    python中,一般能够被for循环遍历的对象就是可迭代对象。
    拥有__iter__()方法的对象称之为可迭代对象,__iter__()方法返回一个迭代器。

    迭代器

    迭代器是访问集合内元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素都被访问一遍后结束。
    可以使用工厂函数iter()返回一个迭代器。

    >>> iter([1,2,3])
    <listiterator object at 0x1100b6a50>
    

    for循环遍历可迭代对象过程

    1. Python将对关键字in后的对象调用iter函数获取迭代器
    2. 调用迭代器的next方法获取元素,直到抛出StopIteration异常。
    3. 对迭代器调用iter函数时将返回迭代器自身,所以迭代器也可以用于for语句中,不需要特殊处理。
      代码如下
    it=iter(lst)
    try:
          while True:
              val=it.next()
              print val
    except
          StopIteration:
              pass
    

    正文

    在stackoverflow中见到这样一个问题 What does the "yield" keyword do in python

    其中排名最高的回答对我有很大帮助,因此将其翻译下来分享给大家答案
    一下是译文:

    要理解什么是yield关键字,必须要理解什么是生成器,而要理解生成器,首先要理解什么是迭代器

    迭代器

    当你生成了一个list,可以一个接一个地访问这个list中的元素,这种行为被称为迭代。

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

    上面代码中的mylist就是一个迭代器。list类型是可迭代的。

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

    在python中可以通过"for... in ..."这种方式遍历的都是迭代器,像lists,strings,files...

    迭代器是很好用的,因为你可以很方便地遍历其中的元素。但是这些数据都是存在内存里的,当数据量非常大时,这种方式就不是非常理想了。

    生成器

    生成器是迭代器的一种,但是只能被迭代一次。这是因为生成器并不会将所有的数据存在内存里,而是在用到的时候生成数据。

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

    上面用[]生成了一个迭代器,而这里用()生成了一个生成器。
    但是再执行

    for i in mygenerator:
        print(i)
    

    不会有任何输出,因为生成器只能使用一次。之前一次遍历中,生成器计算的到0,不存储,然后计算得到1,不存储,最后计算得到4。

    yield

    Yield有点像return,不同的是yield会返回一个生成器

    >>> 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关键词就很有用了。
    要理解yield,你必须了解当返回生成器的函数被调用时,里面的代码实际上并没有运行。这个函数只是返回了一个生成器的对象。这有点让人难以理解。

    在for循环中,这个生成器才会被使用到。

    现在进入困难的部分:
    for循环中,生成器第一次被调用到时,返回这个生成器的函数会顺序执行到yield部分,然后返回这个循环的第一个值。每次调用到生成器,都会执行函数中的循环一次,然后返回下一个值,直到没有值被返回。

    当函数不再执行到yield的时候,生成器为空。这可能是循环结束了或者不再满足"if/else"判断。

    回答题主的问题

    生成器

    # Here you create the method of the node object that will return the 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
      # there is no more than two values: the left and the right children
    
    # 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的同时,也在往list里面添加元素。这种方式可以很简洁遍历所有相邻的数据,虽然有可能造成无限循环。
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
    

    上面的代码会返回所有的生成器,但是while不断得产生新的生成器。

    • extend()方法是list的一个方法,传入一个迭代器,然后将其加入到list中

    我们一般这样用extend

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

    在上面的代码中,传入了一个生成器,这样做有两个好处

    1. 不需要读两次数据
    2. 字节点不用都存在内存中

    上面的代码是可行的,因为python并不关心传入的参数是否是一个list。它关心传入的是不是一个迭代器,所以strings,lists,tuples,generators都是可以作为参数传入的!这叫做鸭子类型,也是python如此受欢迎的原因之一。

    控制生成器

    >>> class Bank(): # let's create a bank, building ATMs
    ...     crisis = False
    ...     def create_atm(self):
    ...         while not self.crisis:
    ...             yield "$100"
    >>> hsbc = Bank() # when everything's ok the ATM gives you as much as you want
    >>> corner_street_atm = hsbc.create_atm()
    >>> print(corner_street_atm.next())
    $100
    >>> print(corner_street_atm.next())
    $100
    >>> print([corner_street_atm.next() for cash in range(5)])
    ['$100', '$100', '$100', '$100', '$100']
    >>> hsbc.crisis = True # crisis is coming, no more money!
    >>> print(corner_street_atm.next())
    <type 'exceptions.StopIteration'>
    >>> wall_street_atm = hsbc.create_atm() # it's even true for new ATMs
    >>> print(wall_street_atm.next())
    <type 'exceptions.StopIteration'>
    >>> hsbc.crisis = False # trouble is, even post-crisis the ATM remains empty
    >>> print(corner_street_atm.next())
    <type 'exceptions.StopIteration'>
    >>> brand_new_atm = hsbc.create_atm() # build a new one to get back in business
    >>> for cash in brand_new_atm:
    ...      print cash
    $100
    $100
    $100
    $100
    $100
    $100
    $100
    $100
    $100
    ...
    

    生成器可以做很多事情,上面代码展示了如何利用yield控制资源的访问

    Itertools-最好的朋友

    itertools模块中有很多控制生成器的方法。

    看下面的例子,看看四匹马赛跑可能的顺序组合

    >>> horses = [1, 2, 3, 4]
    >>> races = itertools.permutations(horses)
    >>> print(races)
    <itertools.permutations object at 0xb754f1dc>
    >>> print(list(itertools.permutations(horses)))
    [(1, 2, 3, 4), 
    (1, 2, 4, 3), 
    (1, 3, 2, 4), 
    (1, 3, 4, 2), 
    (1, 4, 2, 3), 
    (1, 4, 3, 2), 
    (2, 1, 3, 4), 
    (2, 1, 4, 3), 
    (2, 3, 1, 4), 
    (2, 3, 4, 1), 
    (2, 4, 1, 3), 
    (2, 4, 3, 1), 
    (3, 1, 2, 4), 
    (3, 1, 4, 2), 
    (3, 2, 1, 4), 
    (3, 2, 4, 1), 
    (3, 4, 1, 2), 
    (3, 4, 2, 1), 
    (4, 1, 2, 3), 
    (4, 1, 3, 2), 
    (4, 2, 1, 3), 
    (4, 2, 3, 1), 
    (4, 3, 1, 2), 
    (4, 3, 2, 1)]
    

    了解生成器的实现机制

    迭代意味着,调用可迭代对象的iter()方法和迭代器的next()方法

    相关文章

      网友评论

        本文标题:python中的yield关键字

        本文链接:https://www.haomeiwen.com/subject/oediettx.html