生成器介绍
生成器也称之为自定义迭代器,它也有 iter 和 next 方法,和迭代器一样,生成器对象也是节省存储空间的,不过是由我们自己编写代码产生。
创建生成器
关键字 yield
创建生成器我们需要用到关键字 yield
调用生成器运行后,每次遇到关键字 yield 函数就会暂停并返回 yield 的值, 继续调用的话会在当前暂停
的位置继续运行。
代码示例一
def index():
while True:
print('XWenXiang')
yield 'aaa'
res = index()
print(next(res))
print(next(res))
print(index())
定义函数 index() ,在函数体里使用关键字 yield ,我们打印 index() 后可以看到它返回的是生成器
也就是说,在函数里面使用关键字 yield 会将函数变为生成器。
既然函数变成了生成器,那么也可以使用方法 __next__ ,我们对函数调用 next() 后会打印出一句文本
但是并没有被函数体中的 while 一直死循环,因为函数遇到关键字 yield 会暂停,并返回值。我们如果还
要输出可以再一次调用方法 next() 。
代码示例二
def index():
print('XWenXiang')
yield 'aaa'
res = index()
print(next(res))
print(next(res))
如果只有一个关键字 yield 的话,我们只能取一次值,还要调用方法 next() 的话程序会报错,相当
于迭代器的元素被取完了。我们可以在函数里面加多个关键字 yield 如下示例
代码示例三
def index():
print('XWenXiang')
yield 'aaa'
print('from china')
yield 'bbb'
res = index()
print(next(res))
print(next(res))
输出结果
XWenXiang
aaa
from china
bbb
在函数里使用了俩个关键字 yield ,此时调用方法 next() ,第一次调用会在第一个关键字 yield 这暂
停并返回关键字后跟着的值,第二次使用方法 next() 会在第二个关键字 yield 这停止并返回关键字后面
的值。和迭代器一样,不能返回,所以在取值就会报错(也可以使用 for 取值)。
关键字 yield 的作用
1.在函数体代码中出现 可以将函数变成生成器
2.在执行过程中 可以将后面的值返回出去 类似于 return
3.还可以暂停住代码的运行
4.还可以接收外界的传值(了解)
接收外界的值
将关键字 yield 赋值给变量
代码示例一
def index():
print('XWenXiang')
while True:
country = yield
print(f'I come from {country}')
res = index()
print(res.__next__())
输出结果
XWenXiang
None
输出是 None ,我们可以通过方法' send() '来给生成器传值。
代码示例二
def index():
print('XWenXiang')
while True:
country = yield
print(f'I come from {country}')
res = index()
print(res.__next__())
res.send('china')
输出结果
XWenXiang
None
I come from china
* send() 方法会将值传入生成器且自动调用一次方法 next()
* 注意事项: 必须先调用一次方法 next() 后在传值,不然会报错。因为生成器未启动之前,是不能传递数
值进去。
生成器表达式
在前面的生成式文章中说没有元组生成式,其实它是一个生成器
表达式语法
res = (i for i in range(1,10))
和列表生成式差个括号
代码示例
res = (i for i in range(1,10))
print(res)
print(next(res))
输出结果
<generator object <genexpr> at 0x0000022F26D0E5E0>
1
我们可以看待打印的结果是一个生成器
----------------生成器内部的代码只有在调用__next__迭代取值的时候才会执行-------------------
'''生成器表达式内部的代码只有在调用__next__迭代取值的时候才会执行'''
代码练习
def add(n, i):
return n + i
def test():
for i in range(4):
yield i
g = test()
for n in [1, 10]:
g = (add(n, i) for i in g)
res = list(g)
print(res)
此时打印的结果会是几?
1. 首先定义函数 add() 用于返回相加值
2. 定义函数 test() ,但函数里有关键字 yield ,也就是说它是生成器。
3. 调用 test() 赋值给变量 g
4. 定义 for 循环,可迭代变量是 [1, 10] ,也就是只会循环俩次,n 分别为 1,10。
5. 在 for 循环体里面定义了一个生成器表达式并赋值给 g
6. 循环第一次的 g 等价于 (add(n, i) for i in g),'但是代码并不执行,因为没有调用 next 方法'
7. 循环第二次,也就是当 n 等于 10 的时候,此时 g 等价于:
g = (add(n, i) for i in (add(n, i) for i in g))
8. 代码 res = list(g) 先看赋值符号右边,将 g 转化成列表,其实转化成列表的过程底层相当于执行了
方法 next() 将数据取出。
9. 既然执行了 next() ,生成器表达式 g= (add(n, i) for i in (add(n, i) for i in g)) 会被
运行, 而此时的 n 为 10 ,表达式括号里面的 g 表示的是 test() 这个生成器,通过 for 循环取出了
test() 里面的值 0,1,2,3 。于是执行函数 add() 后的到的值 10,11,12,13 组成元组被外层的那
个 for 循环依次取出又执行一次函数 add() 得到最终的值并形成列表赋值给变量 res
10. 所以最后打印的结果是列表 [20, 21, 22, 23]
自定义 range 函数
我们知道 range 函数会生成连续的数字集,它在 Python2 中返回的是列表,而在 Python3 中返回的是一个可迭代对象(类型是对象),而不是列表类型, 所以打印的时候不会打印列表
range 函数有三个参数,为 开始、结束的位置,步长。我们可以通过代码来模拟 range 函数的功能。
代码示例
def my_range(start=0, end=None, step=1):
if not end:
end = start
start = 0
while start < end:
yield start
start += step
for i in my_range(10, step=2):
print(i)
此时的输出结果和 range 函数一样
1. 首先定义函数 my_range 并传入三个参数。
2. 我们先实现 range 基本功能,在函数体里创建 while 循环并使用关键字 yield 的特性能返回值,输
出从参数 start 开始,end 结束的数字。此时因为关键字 yield 的原因,函数变成了生成器。
3. 再然后我们要考虑的是 range 函数只输入一个参数也能达到效果。我们可以先将结束的位置设成 None
因为开始的位置默认为 0 ,当只传入一个参数的时候由于位置参数的原因,我们要将开始的位置赋值给结束
的位置,再将开始的位置修改为 0 就可以实现单个参数的情况了。
4. 还需要考虑的是 range 函数的步长,步长是间隔的距离,我们在定义函数的时候将第三个参数也就是步
长默认设成了 1 ,我们可以在 while 循环里修改自增的值即可。