这个概念较难理解,我们可以通过解答以下几个问题来掌握它。
本节会用到
可迭代对象
及列表生成器
知识,建议先看下之前的文章进行了解。【python实用特性】- 迭代、可迭代对象、迭代器
【python实用特性】- 列表生成式
1、什么是生成器?
生成器: 生成
,很好理解,创建一个对象的意思。那器
是什么呢?器可以理解为工具的意思,所以生成器,就是按照一定规则,创建某种对象的工具。
2、为何要用生成器?有何好处?
举个简单的例子,我们要创建一个100个元素的列表。很简单,list(range(100))
,那如果要创建一个100万、1000万元素的列表呢?
诚然,我们依旧可以使用list(range(10000*100)
、list(range(10000*1000)
来达到目的。
但这样会带来几个问题:
- 变量存储于内存中,所以列表长度受限于内存大小,是有限的
- 列表需要将所有元素一次性创建完成后才能操作,数据过大时,内存使用会瞬间升幅,存在内存不足风险
- 大多数时候只需要用到部分元素,这就造成了内存空间的浪费
为了解决以上问题,于是引入了一种不用一次性全部生成,可以按需逐次获取数据的方案——生成器
所以使用生成器的好处也很明显:
(1)不用一次性生成所有数据,能有效节约内存
(2)可以按需逐次获取数据,不会造成内存空间的浪费
3、python中如何实现生成器?
一般来说,python中的生成器有两种实现方式。
-
普通实现
将列表生成式的[]
改为()
即可,实例如下:g = (x for x in range(10)) print(type(g)) #type查看g的类型 from typing import Iterable print(isinstance(g,Iterable)) #判断是否是可迭代对象
输出:
<class 'generator'> True
可见,此时的
g
便是一个生成器,同时也是一个可迭代对象!
这意味着我们可以使用next(g)
进行 取值 -
yield实现
实例如下,打印0~2#自然数生成器 def getN(N): n = 0 while n<N: yield n #使用yield 返回数据 n+=1 g = getN(3) print(next(g)) print(next(g)) print(next(g))
输出:
0 1 2
如上,我们在函数
getN(N)
中使用了yield
关键字返回数据,所以这个函数就变成了一个生成器。
小结: 若一个函数中使用了yield
返回数据,那这个函数就是一个生成器。
4、yield生成器的运行机制
通过上面的学习,我们已经知道如何去构造一个生成器。那它是如何运行的呢?又为何说它是逐次获取数据呢?
先看一个例子
def test():
print('返回第一个数据:')
yield 'data1'
print('返回第二个数据:')
yield 'data2'
print('返回第三个数据:')
yield 'data3'
g = test()
print(next(g))
输出:
返回第一个数据:
data1
可见,有三个yield 返回,但因为我们只使用了一次 next()
函数,所以只打印了data1
,第一个yield
返回的数据。
那调用两次呢?
print(next(g))
print(next(g))
输出:
返回第一个数据:
data1
返回第二个数据:
data2
小结: 调用一次 next()
函数,便执行一次生成器,遇到yield
关键字后,将数据返回。之后生成器暂停运行,保存当前的运行状态,等待下一个next()
调用。
5、使用循环来迭代生成器
上面的例子中,我们都是通过next()
函数来取值。这种方式当数据量大了之后,就会变得很麻烦。所以需要使用更高效的方法,而因为生成器也是一个可迭代对象,所以我们可以使用循环来进行迭代。
打印0~2的实例改写如下:
-
while循环
def getN(N): n = 0 while n<N: yield n n+=1 g = getN(10) while True: try: print(next(g),end='\t') except StopIteration: break #跳出循环
注:使用
while
循环实现时,要用try...except
捕获StopIterator
异常。 -
for循环
def getN(N): n = 0 while n<N: yield n n+=1 g = getN(3) for data in g: print(data)
注:
for
循环会自动调用next()
函数,且捕获异常。推荐使用。