生成器(Generator)

2018-05-26  本文已影响0人  江山画_孤影

生成器(generator)是一种特殊的迭代器,它的实现更简单优雅。创建生成器的方法主要有两种:生成器函数和生成器表达式

生成器函数

只要函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数,会返回一个生成器对象.

>>>def func(): 
>>>    return  #普通函数

>>>def gen_func():  
>>>    yield   #生成器函数
生成器函数示例
>>>def gen_123():
>>>    yield 1
>>>    yield 2
>>>    yield 3
>>>g = gen_123() #这一步生成器函数还没有运行
>>>next(g)  # 启动生成器函数,遇到yield表达式后停止
1
>>>next(g)  
2
>>>next(g)  
3
>>>next(g)
Traceback (most recent call last):
  File "<pyshell#37>", line 1, in <module>
    next(g)
StopIteration

【问题】
>>>next(gen_123())  
>>>next(gen_123())  
>>>next(gen_123())  
得到的结果始终是1,只有对其使用for语句迭代时
>>> for i in gen_123():
>>>    i
1
2
3

【解释】
>>>next(gen_123())  #第一次调用gen_123函数
>>>next(gen_123())  #第二次调用gen_123函数
>>>next(gen_123())  #第三次调用gen_123函数

gen_123()是对gen_123函数的调用,返回的是生成器对象.但是每次调用,都会返回新的生成器对象
所以上面的3行代码等价于
>>>a = gen_123()
>>>next(a)
>>>b = gen_123()
>>>next(b)
>>>c = gen_123()
>>>next(c)
这样得到的结果当然都是1

而
>>>g = gen_123()
>>>next(g)
>>>next(g)  
....
对gen_123函数调用了一次,返回的生成器对象赋予给了g,然后对g使用next(),依次获取生成器对象的值

>>> for i in gen_123()
for 机制的作用与 g = iter(gen_123()) 一样,用于获取生成器对象,然后每次迭代时调用next(g)

生成器表达式

1.什么是生成器表达式?

所谓的生成器表达式,你可以理解为列表推导的惰性版本:
不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素

2.为什么需要生成器表达式?

比如对一个大文本进行操作,但是我们只要开头的几行,
使用列表推导式,会一次性把文本创建出来,这显然很浪费内存,而生成器此时就派上了用场;
另外像斐波那契这种无穷序列,根本无法创建出来,要获取值还是生成器.

3.生成器表达式
生成器表达式和列表推导式在形式上的唯一区别就在于使用了圆括号()。

L = [x**2 for i in range(10)] #列表推导式
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

G = (x**2 for i in range(10))  #生成器表达式
<generator object <genexpr> at 0x104feab40>

4.和生成器函数一样,生成器表达式返回的也是生成器对象,
必须对其进行for迭代,才能得到里面的值.

>>>def gen_AB():
>>>    print('start')
>>>    yield 'A'
>>>    print('continue')
>>>    yield 'B'
>>>    print('end')

>>>res1 = [x*3 for x in gen_AB()]  
start
continue
end

>>>res1
start
continue
end
['AAA', 'BBB']
列表推导迫切地迭代 gen_AB() 函数生成的生成器对象产出的元素:'A' 和 'B'.
>>>res2 = (x*3 for x in gen_AB())
>>>res2 #res2是一个生成器对象
<generator object <genexpr> at 0x006245A0>
>>>for i in res2:
>>>    i
start
AAA
continue
BBB
end

只有 for 循环迭代 res2 时,gen_AB 函数的定义体才会真正执行。
for 循环每次迭代时会隐式调用 next(res2),前进到 gen_AB 函
数中的下一个 yield 语句。注意,gen_AB 函数的输出与 for 循环中
print 函数的输出夹杂在一起。

惰性计算与内存优化

生成器保存的不是值,而是算法.只有当调用next()的时候,生成器才会产出一个值或者抛出异常。
而列表则是一次性将所有的值都创建出来,当文件非常大而我们只需获取其中一部分值或者是一个无穷序列
这种处理方式将会极大占用内存.而生成器的这种惰性方式获取值的好处则完美体现了出来.

>>>sum([i for i in range(100000000)])
>>>sum(i for i in range(100000000))

相比于上面的列表推导式,生成器除了延迟计算,减少内存占有,同时还能有效提高代码可读性

生成器与迭代器的关系

所有的生成器都是迭代器,因为生成器都自动实现了迭代器协议

>>>g = (x**2 for x in range(3))
>>>g  #生成器对象
<generator object <genexpr> at 0x01403CD8>

>>>next(g) #g.next()不再使用
0
>>>next(g)
1
>>>next(g)
4
>>>next(g)
Traceback (most recent call last):
  File "<pyshell#14>", line 1, in <module>
    next(g)
StopIteration

关于迭代器与生成器更多的区别请参考《流畅的python》一书.

结尾

最后,我们再来回顾一下那幅图,是不是感觉很清晰明了了呢!


上一篇下一篇

猜你喜欢

热点阅读