文章目录
- 生成器
- ①初识生成器
- ②生成器的构建方式
- ③生成器函数
- ④讲解yield from
生成器
①初识生成器
什么是生成器?这个概念比较模糊,各种文献都有不同的理解,但是核心基本相同。生成器的本质就是迭代器,在python社区中,大多数时候都把迭代器和生成器是做同一个概念。不是相同么?为什么还要创建生成器?生成器和迭代器也有不同,唯一的不同就是:迭代器都是Python给你提供的已经写好的工具或者通过数据转化得来的,(比如文件句柄,iter([1,2,3])。生成器是需要我们自己用python代码构建的工具。最大的区别也就如此了。
②生成器的构建方式
在python中有三种方式来创建生成器:
- 通过生成器函数
- 通过生成器推导式
- python内置函数或者模块提供(其实1,3两种本质上差不多,都是通过函数的形式生成,只不过1是自己写的生成器函数,3是python提供的生成器函数而已)
③生成器函数
我们先来研究通过生成器函数构建生成器。
首先,我们先看一个很简单的函数:
def func():
print(11)
return 22
ret = func()
print(ret)
# 运行结果:
11
22
将函数中的return换成yield,这样func就不是函数了,而是一个生成器函数
def func():
print(11)
yield 22
我们这样写没有任何的变化,这是为什么呢? 我们来看看函数名加括号获取到的是什么?
def func():
print(11)
yield 22
ret = func()
print(ret)
# 运行结果:
<generator object func at 0x000001A575163888>
运行的结果和最上面的不一样,为什么呢?? 由于函数中存在yield,那么这个函数就是一个生成器函数。
我们在执行这个函数的时候.就不再是函数的执行了,而是获取这个生成器对象,那么生成器对象如何取值呢?
之前我们说了,生成器的本质就是迭代器。迭代器如何取值,生成器就如何取值。所以我们可以直接执行next()来执行以下生成器
def func():
print("111")
yield 222
gener = func() # 这个时候函数不会执⾏. ⽽是获取到⽣成器
ret = gener.__next__() # 这个时候函数才会执⾏
print(ret) # 并且yield会将func生产出来的数据 222 给了 ret。
结果:
111
222
并且我的生成器函数中可以写多个yield。
def func():
print("111")
yield 222
print("333")
yield 444
gener = func()
ret = gener.__next__()
print(ret)
ret2 = gener.__next__()
print(ret2)
ret3 = gener.__next__()
# 最后⼀个yield执⾏完毕. 再次__next__()程序报错
print(ret3)
结果:
Traceback (most recent call last):
111
222
File "C:/Users/Administrator/Desktop/代码目录/python基础模块/3.py", line 45, in <module>
333
ret3 = gener.__next__()
444
StopIteration
大家可以看见到报了一个迭代器的常见错误:StopIteration,所以我们减少一个next程序就不会报错了。
def func():
print("111")
yield 222
print("333")
yield 444
gener = func()
ret = gener.__next__()
print(ret)
ret2 = gener.__next__()
print(ret2)
# 结果:
111
222
333
444
当程序运行完最后一个yield,那么后面继续运行next()程序会报错,一个yield对应一个next,next超过yield数量,就会报错,与迭代器一样。
yield与return的区别:
return一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值。
yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素。
举例:
我们来看一下这个需求:我们现在需要10000个鸡蛋,我们需要一下子要小鸡(只有一只)生1000个鸡蛋出来。
def egg():
lst = []
for i in range(1,10001):
lst.append('鸡蛋'+str(i))
return lst
e = egg()
print(e)
这样做有一个小问题,我们这个小店呢,一天只能用1000个鸡蛋,然鹅现在还有9000个要放在一边,又要浪费空间去存储他们之余,还要担心他们会不会坏掉,但是如果我们要小鸡在我们需要一个的时候就生成一个,需要一个的时候就生成一个。
def egg():
for i in range(1,10001):
yield f"第{i}个鸡蛋"
e = egg()
# 第一天使用的鸡蛋
for i in range(0,100):
print(next(e))
# 第二天使用的鸡蛋
for i in range(0,100):
print(next(e))
# 结果:
第1个鸡蛋
第2个鸡蛋
第3个鸡蛋
第4个鸡蛋
..
..
..
第198个鸡蛋
第199个鸡蛋
第200个鸡蛋
这两者的区别:
- 第一种是直接把鸡蛋全部生出来,占用内存。
- 第二种是吃一个生产一个,非常的节省内存,而且还可以保留上次的位置。(yield并不会从头执行函数,而是一直停留在上一次next的地方,大家可以去试一试)
人话讲解
- 直接生成一个可迭代对象,如果数据量太过于庞大,我们的内存空间就会极大的被占用,这样的作法是很不科学的,但是yield可以帮忙解决一下这个问题,当产生第2个鸡蛋的时候,第1个鸡蛋占用的空间已经释放 --> 当产生第3个鸡蛋的时候,第3个鸡蛋占用的空间已经释放 – > 这样我们的内存中只有第n个鸡蛋的空间被占用,而不是10000个鸡蛋全部在我们的内存空间中。
④讲解yield from
我的理解是将这个from理解成一个for…他会将这个可迭代对象(列表)的每个元素当成迭代器的每个结果进行返回。
在python3中提供一种可以直接把可迭代对象中的每一个数据作为生成器的结果进行返回
def func():
lis = [11,22,33,44,55,66,77,88]
yield from lis
f = func()
while 1:
try:
print(next(f))
except:
break
# 结果
11
22
33
44
55
66
77
88
那么我们如果写两个yield from呢?会有怎么样的结果?
def func():
lis = [11,22,33,44,55,66,77,88]
lis2 = [101,202,303,404,505,606,707,808]
yield from lis
yield from lis2
f = func()
while 1:
try:
print(next(f))
except:
break
# 结果
11
22
33
..
..
606
707
808
可以理解成将两个列表加起来一个一个返回了。