# 迭代器到生成器

# 迭代器

  • 在 Python 中一切皆对象,对象的抽象就是类,而对象的集合就是容器。

  • 所有的容器都是可迭代的(iterable)。

    • 迭代和枚举不完全一样。迭代可以想象成是你去买苹果,卖家并不告诉你他有多少库存。这样,每次你都需要告诉卖家,你要一个苹果,然后卖家采取行为:要么给你拿一个苹果;要么告诉你,苹果已经卖完了。你并不需要知道,卖家在仓库是怎么摆放苹果的。
  • 把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__​​与 __next__​​。

    • 迭代器(iterator)提供了一个 next 的方法。
    • 而可迭代对象,通过 iter() 函数返回一个迭代器,再通过 next() 函数实现遍历。for in​​语句将这个过程隐式化。
    • 除了for​​能接收可迭代对象,list、set、tuple​​等也能接收。
    class Fib(object):
      
        def __init__(self, num):
            self.num = num
            self.a, self.b = 0, 1
            self.idx = 0
       
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.idx < self.num:
                self.a, self.b = self.b, self.a + self.b
                self.idx += 1
                return self.a
            raise StopIteration()
    
  • 可以通过isinstance​的方法来判断一个对象是否可迭代

    In [1]: from collections import Iterable
    
    In [2]: isinstance([], Iterable)
    Out[2]: True
    

# 生成器

# 生成器概述

  • 生成器是懒人版本的迭代器,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

  • 生成器在调用 next() 函数的时候,才会生成下一个变量,因此并不会占用大量内存。

  • 迭代器是一个有限集,生成器则是一个无限集。

  • 列表生成式中的[]改为()后结果也是一个生成器

  • yield 是生成器魔术的关键。

    • 对于初学者来说可以理解为,函数运行到这一行的时候,程序会从这里暂停,然后跳出到next函数,yield的部分实际上成了next函数的返回值。

    • 使用了 yield​​ 关键字的函数不再是函数,而是生成器。

    • yield​​ 关键字有两点作用

      • 保存当前上下文环境即运行状态(断点),然后暂停执行,即将生成器(函数)挂起
      • yield​​ 关键字后面表达式的值作为返回值返回,此时可以理解为起到了 return​​ 的作用
    def fib(num):
        a, b = 0, 1
        for _ in range(num):
            a, b = b, a + b
            yield a
    
  • 可以使用send​​来唤醒一个生成器,并可在唤醒的同时传入一个附加数据

    def fun():
        i = 0
        while i<5:
            temp = yield i # temp 用来接收send发送过来的值
            print(temp)
            i+=1
    
    
  • 生成器在触发下一个yield之前就结束时会触发StopIteration 异常,异常的值就是生成器return的值

    def example():
        value = yield 2
        print("get", value)
        return value
    
    g = example()
    # 启动生成器,我们会得到 2
    got = g.send(None)
    print(got)  # 2
    
    try:
        # 再次启动 会显示 "get 4", 就是我们传入的值
        got = g.send(got*2)
    except StopIteration as e:
        # 生成器运行完成,将会print(4),e.value 是生成器return的值
        print(e.value)
    

# yield和send

  • 使用yield的函数会保存上一次执行的状态并在下一次执行时接着上次的状态继续执行,一般使用next即可获取下一个值,最常见的做法是生成一个巨长数列的前几项。

    • 简而言之,使用yield的函数会变成一个generator(生成器)。每次执行代码时执行到yield就会返回一个值,下次执行就从yield下接着执行。
  • python的yield可以接受调用者发出的参数,使用send即可,一般认为send(None)和next()一致。

    • n = yield r​​ 可以理解为yield在发送r的同时也在接收n值。
    • 赋值语句先执行右边,即返回r值后子程序便退出,而n的赋值是在下一次启动时首先执行。
    • send发送的值实际是占用了上一次执行末尾yield的位置,因此在生成器未启动之前不能传值进去,必须先用send(None)或者next()来返回生成器第一个值。
def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' %n)
        r = '200 OK'
 
def produce(c):
    c.send(None) # 启动生成器
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' %n)
        r = c.send(n) # 一旦n有值,则切换到consumer执行
        print('[PRODUCER] Consumer return: %s'%r)
    c.close() # 关闭生成器

c = consumer()
produce(c)
[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK

# 生成器的作用

很多时候我们使用生成器的作用是不希望一次返回大量数据-一是占用内存,二是有因此拖慢程序的风险。