一. 什么是生成器?
生成器可以理解成是一种数据类型, 特殊地是生成器可以自动实现迭代器协议
其他的数据类型需要调用自己内置的__iter__方法
所以换种说法, 生成器就是可迭代对象
! 回忆: 很重要的迭代器协议
对象必须提供一个 next 方法, 执行该方法要么返回迭代中的下一项,
要么就引起一个 Stoplteration 异常, 以终止迭代(只能往后走不能往前退)
二. 生成器的分类(两类)
python 中生成器的表现形式
python 中提供生成器的方式
一类是生成器函数; 另一类是生成器表达式
第一类: 关于生成器函数
与常规函数定义相同. 但是返回值时使用 yield 而不是 return.
yield 语句一次返回一个结果, 可以进行多次返回(而 return 只能返回一次)
yield 每次返回一个结果, 在每个结果中间, 挂起函数的状态(其实就是记住我函数执行到哪一行了)
- # 举例:
- def test ():
- yield 1
- g = test() #并不会执行 test()函数, 需要通过 g.__next__()方法来触发生成器函数执行
- print(g)
- print(g.__next__())
- # 执行结果
- <generator object test at 0x0051AA70>
- 1
在说生成器表达式之前, 补充三元表达式和列表解析
三元表达式:(顾名思义, 就是有三个元素呗)
以前我们是这么写程序的:
- name = 'alex'
- if name == 'alex':
- print('Ok')
- else:
- print('Not ok')
利用三元表达式我们是这么写程序的:
- name = 'alex'
- res = 'Ok' if name == 'alex' else 'Not ok' #三元表达式
- print(res)
- # 执行结果
- Ok
(每一圈为一个元)
列表解析:
列表解析式的语法格式为:
[i 操作 for i in 列表 if 表达式 1 and 表达式 2]
(其实就是用中括号 [] 将三元表达式框起来)
举例理解:
- # 我要通过程序下 10 个鸡蛋
- # 以前我是这么写的
- egg_list = []
- for i in range(10):
- egg_list.append('鸡蛋 %s' %i)
- print(egg_list)
- # 执行结果
- ['鸡蛋 0', '鸡蛋 1', '鸡蛋 2', '鸡蛋 3', '鸡蛋 4', '鸡蛋 5', '鸡蛋 6', '鸡蛋 7', '鸡蛋 8', '鸡蛋 9']
- # 通过列表解析式我是这么写程序的
- l0 = [ '鸡蛋 %s' %i for i in range(10) ]
- print(l0)
- # 执行结果
- ['鸡蛋 0', '鸡蛋 1', '鸡蛋 2', '鸡蛋 3', '鸡蛋 4', '鸡蛋 5', '鸡蛋 6', '鸡蛋 7', '鸡蛋 8', '鸡蛋 9']
- l1 = [ '鸡蛋 %s' %i for i in range(10) if i <5 ]
- print(l1)
- # 执行结果
- ['鸡蛋 0', '鸡蛋 1', '鸡蛋 2', '鸡蛋 3', '鸡蛋 4']
- l2 = [ '鸡蛋 %s' %i for i in range(10) if i < 3 or i> 7]
- print(l2)
- # 执行结果
- ['鸡蛋 0', '鸡蛋 1', '鸡蛋 2', '鸡蛋 8', '鸡蛋 9']
总结: 列表解析式优缺点
优点: 取值方便(如果列表的长度较小时使用列表解析会很方便,)
缺点: 如果列表的长度很大的时候, 使用列表解析会占用很多的内存资源, 此时可以使用生成器表达式来节省内存资源
第二类: 关于生成器表达式
生成器表达式:(就是将列表解析式的中括号变成圆括号)
- # 举例:
- l0 = ('鸡蛋 %s' %i for i in range(10))
- print(l0)
- print(l0.__next__())
- print(l0.__next__())
- print(l0.__next__())
- # 执行结果
- <generator object <genexpr> at 0x0045AA70>
鸡蛋 0
鸡蛋 1
鸡蛋 2
小结:
1. 将列表解析式的 [] 换成 () 得到的就是生成器表达式
2. 列表解析式与生成器表达式都是一种便利的编程方式, 只不过生成器表达式更节省内存
3.python 使用迭代器协议让 for 循环变得更加通用. 大部分内置函数也是使用迭代器协议来访问对象的
举例:
sum 函数
- sum(x ** 2 for x in range(4)) #sum 直接按照迭代器协议访问对象(类比 for 循环)
- sum([x ** 2 for x in range(4)]) #所以并不需要将对象 x ** 2 for x in rang(4) 加上一个中括号变成列表解析式, 将所有的值取出来构成一个列表再进行求和运算
三, 通过两段程序代码来感受一下生成器的优势
- # 今天所举得列子不是下蛋就是吃包子(视频课上老师就是这么讲的)
- # 我也深深的怀疑
- # 为什么老师这么钟爱吃包子和下鸡蛋
- # 下蛋程序一:
- def xiadan():
- res = []
- for i in range(10000):
- res.append('鸡蛋 %s' %i)
- return res
- print(xiadan())
- # 缺点一: 占用空间较大
- # 缺点二: 效率低
- # 下蛋程序二:
- def xiadan():
- for i in range(10000):
- yield '鸡蛋 %s' %i
- lmj = xiadan()
- print(lmj.__next__())
- # 第一段程序是一旦执行 xiadan()这个函数, 先下了 10000 个鸡蛋来占用内存空间, 在去执行其他操作
- # 第二段程序是通过生成器函数 yield 来返回我所需要的鸡蛋, 我边用 (通过__next__() 触发生成器函数) 鸡蛋, 边下鸡蛋
以生成器函数为例, 对生成器进行总结
语法上和函数类似: 生成器函数和常规函数几乎是一样的. 它们都是使用 def 语句进行定义
差别在于生成器使用 yield 语句进行返回一个值, 而常规函数使用 return 语句返回一个值
自动实现迭代器协议: 对于生成器, python 会自动实现迭代器协议, 以便应用到迭代器背景中
由于生成器自动实现了迭代器协议, 所以我们可以直接调用它的 next 方法, 并且在没有值可以返回的时候生成器自动生成 Stoplteration 异常
状态挂起: 生成器使用 yield 语句返回一个值. yield 语句挂起该生成器函数的状态, 保留足够的信息, 以便之后从它离开的地方继续执行
优点一:
生成器的好处就是延迟计算, 一次返回一个结果. 也就是说它不会一次生成所有的结果, 这对于大数据量
处理非常有用(前面下鸡蛋的例子)
优点二:
生成器还能提高代码可读性
1. 使用生成器以后, 代码行数更少(在保证代码可读性的前提下, 代码行数越少越好)
2. 不适用生成器, 对于每次结果, 我们首先看到的是 result.append(index), 其次才是 index
也就是说, 我们每次看到的, 是一个列表的 append 操作, 只是 append 的是我们想要的结果.
使用生成器的时候, 直接 yield index, 少了列表的 append 操作的干扰, 我们一眼能够看出, 代码是要进行什么操作.
四, 触发生成器执行的三种方式
方式一:__next__()
方式二: next()
方式三: send()
- # 举例:
- def xiadan():
- for i in range(10000):
- yield '鸡蛋 %s' %i
- g = xiadan()
- print(g.__next__())
- print(next(g))
- print(g.send(None))
- # 执行结果
鸡蛋 0
鸡蛋 1
鸡蛋 2
关于 send() 总结来源于一下文章并且结合自己的理解
看到一篇关于 yield 总结特别好的文章
链接: https://www.cnblogs.com/renpingsheng/p/8635777.html
send()必须传一个参数, 可为 None 或者 其他值
作用:
1. yield 相当于 return , 控制的是函数的返回值
2. x = yield 的另外一个特性, 接受 send 传过来的值, 赋值给 x
举例理解:
- def test():
- print('开始执行函数')
- first = yield
- print('第一次', first)
- second = yield
- print('第二次', second)
- yield
- g = test()
- print(next(g))
- print(g.send(1))
- print(g.send(2))
- # 程序执行过程分析
- # 1. 程序开始执行以后, 因为 test 函数中有 yield 关键字, 所以 test 函数并不会真的执行, 而是先得到一个生成器 g.
- # 2. 直到调用 next 方法, test 函数正式开始执行, 先执行 test 函数中的 print 方法, 打印开始执行函数. 然后执行 first = yield
- # 3. 程序遇到 yield 关键字, 程序暂停, 此时 next(g)语句执行完成, 打印 next(g)执行结果, 即 yield 传回的结果, 为 None
- # 4. 程序执行 g.send(1), 程序会从 yield 关键字那一行继续向下运行, send 会把 1 这个值传递给 yield
- # 5.yield 接收到 send 方法传递过来的值, 然后由 yield 赋值给 first 变量
- # 6. 由于 send 方法中包含 next()方法, 所以程序会继续向下运行执行 print 方法, 然后再次遇到 yield 关键字, 程序暂停, 此时 g.send(1)语句执行完成, 打印 g.send(1)执行结果, 即 yield 传回的结果, 为 None
- # 7. 程序执行 g.send(2), 程序会从 yield 关键字那一行继续向下运行, send 会把 2 这个值传递给 yield
- # 8.yield 接收到 send 方法传递过来的值, 然后由 yield 赋值给 second 变量
- # 9. 由于 send 方法中包含 next()方法, 所以程序会继续向下运行执行 print 方法, 然后再次遇到 yield 关键字, 程序暂停, 此时 g.send(2)语句执行完成, 打印 g.send(2)执行结果, 即 yield 传回的结果, 为 None
写在后面:
珍爱眼睛 远离电子产品
从上了研究生阶段 眼睛就开始有虹膜炎
隔段时间就来打扰我
不能看电脑不能看手机不能看强光
还拼命流眼泪
我也真是佩服自己
正好这个阶段我就在看书学 python 也抽时间看了 许三观卖血记
文学素养还是要培养的
我要做祖国新时代的四有新人 有文化 有道德 有... 还有什么来着
哈哈
爱吃火锅的人运气不会太差
爱吃火锅的人怎么可能轻易放弃
来源: https://www.cnblogs.com/guoruxin/p/10067741.html