0
点赞
收藏
分享

微信扫一扫

Python生成器

乐百川 2022-01-11 阅读 178

文章目录


生成器

迭代器是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。

总结

本文简要介绍了生成器的概念,生成器函数的概念,函数yield、send和yield from的用法,以及生成器表达式的写法,最后通过实例说明了生成器的好处。
举报

相关推荐

0 条评论