0%

生成器

1. 什么是generator

列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的,如果创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

如果可以先不创建完整的list,而是先创建一个东西,使得这个东西可以在循环的过程中计算出后面的元素,这样可以节省大量的空间。这个一边循环一边计算的机制,成为生成器:generator

2. 创建generator

<1> 第一种方法:把列表生成式的[]改成()

这里可以看到,函数gen_ner只是把gen_list最外层的[]改成了(),结果就是L是一个list,而g是一个generator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def gen_list():
L = [x * x for x in range(10)]
print(L)
#gen_list的输出结果为:
#[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]


def gen_ner():
g = (x * x for x in range(10))
print(g)
#gen_ner()的输出结果为:
#<generator object gen_ner.<locals>.<genexpr> at 0x000002245435B970>


if __name__ == "__main__":
gen_list()
gen_ner()

从上面的结果可以看到,generator是一个对象,如何打印出放在里面的元素呢,可以一个一个用next()打印,不过用next的话有一个缺点是,需要不断地调用next去计算g的下一个元素(next(g)),这种方法不能指定需要的那个元素,而且在没有更多元素的时候,会抛出StopIteration的错误。

那么更便捷的方法是什么呢?用for循环打印:

1
2
3
4
5
6
7
8
def gen_ner():
g = (x * x for x in range(10))
for n in g:
print(n)


if __name__ == "__main__":
gen_ner()
2.1 例子(斐波拉契数列)

著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, …

1
2
3
4
5
6
7
8
9
10
11
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'


if __name__ == "__main__":
fib(8)
1
2
3
4
5
6
7
8
9
执行结果是:
1
1
2
3
5
8
13
21

其中:a, b = b, a + b为赋值语句,相当于:

1
2
3
t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]

但不必显式写出临时变量t就可以赋值,也就是说,a, b = b, a + b这句话的意思是:把a赋值给b,把b赋值给a+b。t只是作为一个中间变量出现,不用中间变量的时候可以直接赋值。

image-20220424192351161

2.2 斐波拉契数列类似generator

斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator,也就是”2.1 例子(斐波拉契数列)“的函数和generator仅一步之遥。要把fib函数变成generator函数,只需要把print(b)改为yield b就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'


if __name__ == "__main__":
f = fib(8) #调用generator对象
print(f)
1
2
运行的结果是:
<generator object fib at 0x000001EAE925B970>

3. generator函数的执行顺序

普通函数是顺序执行,遇到return语句或者最后一行函数语句就返回。

generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行,例子如下:

先定义一个generator函数,依次返回数字1,3,5:

1
2
3
4
5
6
7
def odd():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)

调用该generator函数时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
step 3
5
>>> next(o)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

可以看到,odd不是普通函数,而是generator函数,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next(o)就报错。

我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

同样的,把函数改成generator函数后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

1
2
3
4
5
6
7
8
9
10
11
12
13
def odd():
print('step 1')
yield 1
print('step 2')
yield 3
print('step 3')
yield 5
yield 10


if __name__ == "__main__":
for n in odd():
print(n)
1
2
3
4
5
6
7
8
结果是:
step 1
1
step 2
3
step 3
5
10

但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def odd():
print('step 1')
yield 1
print('step 2')
yield 3
print('step 3')
yield 5


if __name__ == "__main__":
g = odd()
while True:
try:
x = next(g)
print(x)
except StopIteration as e:
print('Generator return value:', e.value)
break
点这里请我吃个小蛋糕吧~~

Welcome to my other publishing channels