生成器 generator
# 生成器 generator
# 什么是生成器
- 若列表元素可以按照某种算法算出来,就可以在循环的过程中不断推算出后续需要用的元素,而不必创建完整的 list,从而节省大量的空间
- 边循环边计算的机制,叫生成器(generator)
# 最简易生成器
L = [x * x for x in range(10)]
print(L)
print(type(L))
L = (x * x for x in range(10))
print(L)
print(type(L))
# 输出结果
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<class 'list'>
<generator object <genexpr> at 0x7f55431c1970>
<class 'generator'>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
只要把一个列表生成式的 []
改成 ()
,就创建了一个 generator。
列表生成式
可以理解为一种生成列表的简易写法,它跟用 for 循环生成列表是一样的效果。
- 一般写法
res = [i * i for i in range(11)]
1 - 带 if 条件
res = [i * i for i in range(11) if i % 2 == 0]
1 - 双重循环
res = [i + j for i in range(5) for j in range(6, 11)]
1 - 多个变量
obj = {'name': '张三', 'age': '13'} res = [(k, v) for k, v in obj.items()]
1
2 - 包含函数
lis = ['Hello', 'World', 'Java', 'Python'] res = [s.lower() for s in lis]
1
2 - 包含 if...else
# 要将 if...else 放在前面写 res = [i if i % 2 == 0 else "奇数" for i in range(11)]
1
2
# 打印生成器的元素
上面发现直接打印 L
得到的是一个生成器对象,而不是直观可见的一个列表。所以有几种方法来访问生成器的每个元素。
# 通过 for 循环遍历
L = (x * x for x in range(10))
for i in L:
print(i)
1
2
3
4
2
3
4
# next() 方法
可以获取 generator 的下一个元素(目前几乎没用使用过这个)。
L = (x for x in range(10))
print(next(L))
print(next(L))
print(next(L))
print(next(L))
print(next(L))
print(next(L))
# 输出结果
0
1
2
3
4
5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
generator 能够迭代的关键就是 next()
方法,通过重复调用 next()
方法,直到捕获一个异常。
# .__next()__
L = (x for x in range(10))
print(L.__next__())
print(L.__next__())
print(L.__next__())
print(L.__next__())
print(L.__next__())
print(L.__next__())
# 输出结果
0
1
2
3
4
5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# yield 函数
- 带有
yield
的函数不再是一个普通函数,而是一个生成器 generator yield
相当于return
返回一个值,并且记住这个返回值的位置,下次迭代时,代码会从yield
的下一条语句开始执行,直到函数结束或遇到下一个yield
。
# 斐波拉契数列
1, 1, 2, 3, 5, 8, 13, 21, 34, ...,除第一个和第二个数外,任意一个数都可由前两个数相加得到。
不用生成器可以这么实现:
# 斐波拉契数列
res = []
def fib(max):
n, a, b = 0, 0, 1
while n < max:
res.append(b)
a, b = b, a + b
n = n + 1
fib(8)
print(res)
# 输出结果
[1, 1, 2, 3, 5, 8, 13, 21]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
因为知道第一个元素值,就可以推算后面的任意个元素了,所以可以使用生成器来实现:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
res = fib(8)
for i in res:
print(i)
# 输出结果
1
1
2
3
5
8
13
21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 生成器的执行流程
- 普通函数是顺序执行,遇到
return
或者最后一行执行完就返回。 - 生成器的执行流程是
- 每次调用
next()
或for
循环的时候执行,遇到yield
就返回 - 一个生成器里面可以有多个
yield
- 再次执行时从上次返回的
yield
语句处继续执行
- 每次调用
# 执行流程
def odd():
print('step 1')
yield 1
print('step 2')
yield 3
print('step 3')
yield 5
L = odd()
for i in L:
print(i)
# 输出结果
step 1
1
step 2
3
step 3
5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 生成器的工作原理
- 它是在 for 循环过程中不断计算下一个元素,并在适当的条件结束 for 循环。
- 对于函数改成的 generator 来说,遇到
return
语句或者执行到函数最后一行时,就是结束 generator 的指令,for
循环随之结束。
# 生成器的优点
在不牺牲过多速度情况下,释放了内存,支持大数据量的操作。
可以运行下面两种写法的代码,观察电脑内存的变化。
不使用生成器:
from tqdm import tqdm
res = []
for i in tqdm(range(10000000)):
temp = ['你好'] * 2000
res.append(temp)
for ele in res:
continue
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
使用生成器:
def test():
for i in tqdm(range(10000000)):
temp = ['你好'] * 2000
yield temp
res = test()
for ele in res:
continue
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 生成器的应用场景
需要处理大数据量的场景,比如一个文件有几百万行数据,或者有几百万个文件需要分别读取处理。
(完)