生成器
1. 什么是生成器
器乃工具也,生成器就是就来生成某种东西的工具
生成器实际上本质就是迭代器,也是惰性取值,也节省内存
2. 为什么要有生成器
既然生成器本质就是迭代器,那么python为什么还要搞一个生成器呢?他俩有啥区别?实际上迭代器是通过可迭
代对象转换出来的,不是我们想怎么搞就怎么,要取决于可迭代对象,我靠,可迭代对象不是可以自己定义吗?
这样实际上也自定义了迭代器,不也可以吗?python之父可能觉得不爽,我直接搞一个自定义的迭代器,不通过
自定义可迭代对象转换这样不好吗?看不惯就造,于是有了生成器
生成器就是自定义的迭代器,它不依赖可迭代对象,直接造出来,器是工具,工具可以通过函数造出来,所以通过自定义一个特殊的函数,来实现生成器
3. 生成器怎么用
3.1 yield关键字
在函数内部只要有yield关键字,调用函数之后不会执行函数体代码,而是返回一个生成器对象
def func():
print('第一次')
yield 1
print('第二次')
yield 2
print('第三次')
yield 3
print('第四次')
g = func() # 生成器
print(g) # <generator object func at 0x000001E30BDDFF10>
3.2 __next__
方法
生成器调用__next__
方法也会从里面取出一个值
def func():
print('第一次')
yield 1
print('第二次')
yield 2
print('第三次')
yield 3
print('第四次')
g = func()
print(g.__next__()) # 1
# 生成器调用__next__方法会触发函数体代码的运行,然后遇到yield停下来,将yield后的值当做本次调用的结果返回
yield
和return
关键字都可以返回值,但是return会终止函数
, 但是yield不会
, 而是夯住 , 等待下一次取值
也就说yield做到了函数体代码中止,是不是很牛逼,以前的函数体代码都是一股烟全执行完毕,现在我们可以通
过yield关键字,让他先执行一部分,然后主程序去执行其他代码,然后又回来执行函数体剩下的代码,做到了
从代码的切换从函数内切换到外面然后还能切换回来,我靠,比闭包还cool
同样生成器当你取完值再取里面就没有值了,再取就报错。
def func():
print('第一次')
yield 1
print('第二次')
yield 2
print('第三次')
yield 3
print('第四次')
g = func()
print(g.__next__()) # 1
print(g.__next__()) # 2
print(g.__next__()) # 3
print(g.__next__()) # 报错
3.3 for循环生成器
生成器自然而然也是可以被for循环的
def func():
yield 1
yield 2
yield 3
g = func()
for i in g:
print(i) # 1 2 3
3.4 __iter__
方法
同样生成器调用iter方法也是返回自己本身
def func():
print('第一次')
yield 1
print('第二次')
yield 2
print('第三次')
yield 3
print('第四次')
g = func()
print(g) # <generator object func at 0x00000170511EFF10>
print(g.__iter__()) # <generator object func at 0x00000170511EFF10>
4. 为yield传值
yield
关键字不但可以返回值,你还可以在函数外部为其传值,不过要通过指定的方法send()
def dog(name):
print('%s准备吃东西啦...' % name)
while True:
# x拿到的是yield接收到的值
x = yield 111 # x ='肉包子'
print('%s吃了%s' % (name, x))
g = dog('tom') # 得到一个生成器赋值给g
g.send(None) # 等同于g.__next__(),让函数挂起,就是初始化,当刚得到生成器的时候,光标在开头,初始化光标应该在yield后面
res = g.send('肉包子')
print(res)
然后函数体内部的代码执行到下一次yield右边,挂起,同样会把后面的返回值111
返回给这次的调用者即
g.seng("肉包子")
注意 :
刚得到的生成器不能为其yield传非None值,要么你传一个None值,要么你调用g.__next__()
记住每次send()都是在为yield传值,但是,调用send()的返回值是yield后面的值
5. 小总结
优点
-
Python使用生成器提供了延迟操作。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生
结果。这对于大数据量处理,将会非常有用。
-
除了延迟计算,生成器还能有效提高代码可读性。
缺点
- 只能遍历一次
生成器迭代器都可以反复创建,迭代器取决于可迭代对象的存在,只要可迭代器对象在,你就可以造迭代器
取完值,你想还取你就再调用一次__iter__
方法,又会返回一个迭代器,当然你取完值,迭代器里面虽然没有
值了,但是你还以再调用__iter__
方法,循环往复。而生成器取完里面的值,你就要在定义一个新的生成器,才
能继续愉快的取值了,也有人叫这样定义的生成器叫生成器函数
6. 小练习
6.1 自定义一个可以无限生成值的生成器
def nb_g():
count = 0
while 1:
count += 1
yield count
6.2 自定义一个range()生成器
def my_range(start,stop,step):
while start < stop
yield start
strat += step