文章目录
生成器
迭代器是Python解释器内部定义的数据结构。
生成器 (generator)本质就是迭代器,只是可以自定义数据结构和计算过程。
定义生成器有2种方式:
- 生成器函数
- 生成器表达式
1.1 生成器函数
概念:包含yield关键字的函数,就是生成器函数
def getNum():
print("返回1") # return 1 #一般的函数
yield 1 #生成器函数
res = getNum() #一般函数返回数字1;生成器函数返回了生成器对象 :generator
print(res) #<generator object getNum at 0x000001F31DCA12E0>
可以验证生成器对象就是迭代器对象:
from collections.abc import Iterator
print(isinstance(res,Iterator)) #输出:True
既然是迭代器对象,就可以使用next方法来访问其中的元素:
print("运行了next:",next(res)) #输出:返回1 #说明getNum函数执行了
#输出:运行了next: 1 #yield返回的结果输出了
#print("运行了next:",next(res))
如果再次调用上面的打印语句,会报错:StopIteration。
说明迭代器元素遍历到最后,没有可以返回的内容了。由此可以推断:next函数调用依次,yield就执行一次
1.2 yield
中文翻译为:产出 ,相当于返回每次迭代的结果值。
def getNum():
print("返回1")
yield 1
print("返回2")
yield 2
print("返回3")
yield 3
res = getNum()#返回了生成器对象 :generator
print("运行了next:",next(res))
print("运行了next:",next(res))
输出结果:
返回1
运行了next: 1
返回2
运行了next: 2
结论:每执行一次next函数,yield执行一次,返回了一个迭代器元素,并记录了当前执行的yield的代码位置。
既然res是迭代器对象,就可以直接使用for循环来遍历其中的全部元素,同时防止出现StopIteration。
for i in res:
print("运行了next:", i)
输出结果:
返回1
运行了next: 1
返回2
运行了next: 2
返回3
运行了next: 3
有了上面的概念,就可以通过定义自己的生成器函数,实现自定义元素生成过程的迭代器了。
实例:生成指定数量的随机数。所有的随机数范围都在1-10之间。
import random
def getNum(length): #定义一个生成器,length表示生成随机数的数量
for i in range(length):
yield f"当前的数值为:{random.randint(1,10)}"
num = getNum(10) #通过设置生成器函数的参数,指定随机数个数
for i in range(10):
print(next(num)) #每执行一次next,就生成一个[1,10]区间的随机数
print("-"*30)
num = getNum(5) #生成5个随机数,但是内存中始终只有1个数值
for i in range(5):
print(next(num)) #访问的时候,才产生这个随机数,能够节省内存空间
1.3send
send可以在外界获取下一个生成器元素时,传入指定参数来影响生成器的元素产生过程。
def getResult(x):
print("接收到x:",x)
y = yield x * x #在第一次执行完yield返回结果后,代码会停留在这里
#直到send函数从外界传入一个数值,相当于执行给y赋值的功能,代码 继续执行
print("接收到y:", y)
z = yield y ** 3
print("接收到z:", z)
z = yield z + 1000
res = getResult(2)
print("第一次访问:",next(res)) #第一次访问时,需要使用next函数来获取第一个元素
print("第二次访问:",res.send(8))
接收到x: 2
第一次访问: 4
接收到y: 8
第二次访问: 512
如果第一获取元素的时候使用send方法,必须参数是None;否则会报TypeError。
def getResult(x):
print("接收到x:",x)
y = yield x * x #在第一次执行完yield返回结果后,代码会停留在这里
#直到send函数从外界传入一个数值,相当于执行给y赋值的功能,代码 继续执行
print("接收到y:", y)
z = yield y ** 3
print("接收到z:", z)
z = yield z + 1000
res = getResult(2)
#print("第一次访问:",res.send(2)) #报错:TypeError: can't send non-None value
print("第一次访问:",res.send(None))#第一次发送的时候必须参数是None
实例:得到指定数字的平方
def getSquare(num):
#自定义生成器函数
x = num
while True:
x = yield x ** 2
gs = getSquare(1)
print(next(gs)) #写法一
#print(gs.send(None)) #写法二
print(gs.send(10))
print(gs.send(40))
1
100
1600
1.4 yield from
在外界访问生成器时,我们想要将生成器函数中的列表元素,逐一返回给外界。但下面的结果不是我们想要的:
def getNum():
t = [1,2,3,4]
yield t #直接返回的是列表对象
res = getNum()
print(next(res)) #输出:[1, 2, 3, 4]
这个时候我们需要使用yield from ,从可迭代对象中产生数据:逐一返回每个元素
def getNum():
t = [1,2,3,4]
yield from t
res = getNum()
for i in res:
print(i,end=" ") #输出:1 2 3 4
1.5 生成器表达式
与推导式的写法基本相同,也有循环模式、筛选模式、多重循环构建。
只有一点不同:不是 [ ],不是{ } ,而是 ( )
相对于看起来像“元组推导式”( 不存在这个概念 )的意思,但它所生成的结果不是序列对象,而是生成器对象。
g = (i * 2 for i in range(5))
# print(g) #输出的不是元组,而是:<generator object <genexpr> at 0x03E2DDB0>
for i in g:
print(i,end=" ") #遍历生成器对象,依次输出0 2 4 6 8
1.6 生成器的好处
1.延迟计算,一次返回一个结果。换言之它不会一次生成所有的结果,有利于大数据量处理。
使用生成器:执行成功
s = (i for i in range(10000000000))
不使用生成器:卡死
s = [i for i in range(10000000000)]
2.有效提高代码可读性
【实例】求一段文字中,每个空格出现的位置。
*使用生成器:
def index_words(text):
for index, letter in enumerate(text, 1):
if letter == ' ':
yield index
sentence = "一二三四五,上山 打老虎,老虎打不到, 打到小松鼠"
text = list(sentence)
s = index_words(text)
m = 1
for i in s:
print(f"第{m}个空格在第{i}个位置")
m = m + 1
第1个空格在第9个位置
第2个空格在第20个位置
*不使用生成器:
def index_words(text):
result = []
for index, letter in enumerate(text, 1):
if letter == ' ':
result.append(index)
return result
sentence = "一二三四五,上山 打老虎,老虎打不到, 打到小松鼠"
text = list(sentence)
s = index_words(text)
m = 1
for i in s:
print(f"第{m}个空格在第{i}个位置")
m = m + 1
第1个空格在第9个位置
第2个空格在第20个位置
分析:不使用生成器的时候,我们每次看到的是一个列表的append操作,只是append的是我们想要的结果。使用生成器的时候,直接yield index,少了列表append操作的干扰,我们一眼就能够看出,代码是要返回index。