生成器 Generator:
- 自己写的能实现迭代器功能的就叫生成器
- 生成器的本质就是迭代器,所以自带了__iter__方法和__next__方法,不需要我们去实现 和 可以被 for 循环
- 特点:
- 调用函数的之后函数不执行,返回一个生成器
- 每次调用next方法的时候会取到一个值
- 直到取完最后一个,在执行next会报错
- 在调用 生成器函数 和 生成器表达式 的时候只是获取到了生成器,而里面的代码并没有被执行
- 生成器每次只能取 1 个值,且你不向它取值生成器是不会被执行的
- 生成器里的所有数据只能取一次,取完就没有
生成器的好处: 不会一下子在内存中生成太多数据 -> 假如我想让工厂给学生做校服,生产2000000件衣服,我和工厂一说,工厂应该是先答应下来,然后再去生产,我可以一件一件的要,也可以根据学生一批一批的找工厂拿。而不能是一说要生产2000000件衣服,工厂就先去做生产2000000件衣服,等回来做好了,学生都毕业了。。。
创建生成器的两种方式: 生成器函数 生成器推导式
1. 生成器函数 -> 可以被 for 循环
- 一个包含yield关键字的函数就是一个生成器函数, 调用生成器函数不会得到返回的具体的值,而是得到一个生成器(可以理解为 可迭代的对象 或 迭代器),然后再通过 for 循环 或 __next__ 方法获得具体的值。yield语句的作用就是一次返回一个结果,在每个返回结果的中间,挂起函数的状态,以便下次重它离开的地方继续执行
def generator_fn():
print(1)
yield '返回值一'
print(2)
yield '返回值二'
print(3)
yield '返回值三'
generator = generator_fn() # <generator object generator_fn at 0x000002DAC5CEEF68> 返回一个可迭代对象 看到 generator 单词就应该知道它是一个迭代器
print(generator.__next__()) # 返回值一
print(generator.__next__()) # 返回值二
print(generator.__next__()) # 返回值三
def generator_fn():
print(1)
yield '返回值一'
print(2)
yield '返回值二'
print(3)
yield '返回值三'
generator = generator_fn() # <generator object generator_fn at 0x000002DAC5CEEF68> 返回一个可迭代对象 看到 generator 单词就应该知道它是一个迭代器
for i in generator:
print(i)
- 注意:
- 在调用 生成器函数 和 生成器表达式 的时候只是获取到了生成器,而里面的代码并没有被执行
- 生成器每次只能取 1 个值,且你不向它取值生成器是不会被执行的
- 生成器里的所有数据只能取一次,取完就没有
def generator_fn():
print(1)
yield '返回值一'
print(2)
yield '返回值二'
print(3)
yield '返回值三'
generator = generator_fn() # 此时只是获取到了生成器,而生成器并没有执行
for i in generator: # 向生成器取值了,生成器开始执行
print(i)
- 上面提到的校服例子
def uniforms():
for i in range(2000000):
yield '第 %s 套校服' % (i + 1)
g_uniforms = uniforms()
print(g_uniforms.__next__()) # 要一件衣服 # 第 1 套校服
print(g_uniforms.__next__()) # 再要一件衣服 # 第 2 套校服
print(g_uniforms.__next__()) # 再要一件衣服 # 第 3 套校服
for i, u in enumerate(g_uniforms): # 要一批衣服,比如5件 那么会从第 4 件开始拿起,因为 yield 语句会从你上一次结束的位置开始继续执行下去,直到遇见下一个 yield 语句 或 函数执行完
print(u)
if i > 3:
break
for i, u in enumerate(g_uniforms): # 再要一批衣服,比如5件 那么会从第 8 件开始拿起,因为 yield 语句会从你上一次结束的位置开始继续执行下去,直到遇见下一个 yield 语句 或 函数执行完
print(u)
if i > 3:
break
- 监听文件的输入 判断是否输入了 xxx 关键字,然后将有关该关键字这一行输出出来
def tail(filename):
f = open(filename, encoding='utf-8')
f.close()
while True:
line = f.readline().strip()
if line:
yield line
g = tail('monitor.txt')
for i in g:
if 'python' in i:
print('****' + i + '****')
- 从生成器中取值得方法
- __next__()
def generator_fn():
for i in range(10):
yield i
generator = generator_fn()
g_data1 = generator.__next__()
g_data2 = generator.__next__()
- next(生成器) next() 等同于 __next__() 在以后日常使用中尽量使用 next(),在这里使用 __next__() 是为了说明
def generator_fn():
for i in range(10):
yield i
generator = generator_fn()
g_data1 = next(generator)
g_data2 = next(generator)
- for
def generator_fn():
for i in range(10):
yield i
for i in generator_fn():
print(i)
- 将生成器强制转换为 列表、元组、集合 不推荐 占用内存高
def generator_fn():
for i in range(5):
yield i
arr = list(generator_fn()) # [0, 1, 2, 3, 4]
- .send() -> send 的效果和获取下一个值的next效果基本一致,只是在获取下一个值的时候,给上一yield的位置传递一个数据 -> 使用情况: 依赖外部的值 ->很少会使用到
- 使用send的注意事项
- 第一次使用生成器的时候一定用next获取下一个值
- 最后一个yield不能接受外部的值
def generator_fn():
e_data1 = yield 1
print(e_data1) # 外部的值1
e_data2 = yield 2
print(e_data2) # 外部的值2
yield 3
generator = generator_fn()
g_data1 = generator.__next__()
g_data2 = generator.send('外部的值1')
g_data3 = generator.send('外部的值2')
- 例子:移动平均值
def moving_average():
total = 0
count = 0
avg = 0
while 1:
num = yield avg
total += num
count += 1
avg = total / count
generator = moving_average()
generator.__next__()
avg1 = generator.send(10) # 10
avg2 = generator.send(20) # 15
- 预激活生成器的装饰器
def wrapper(fn):
def inner(*args, **kwargs):
re = fn()
re.__next__()
return re
return inner
@wrapper
def moving_average():
total = 0
count = 0
avg = 0
while 1:
num = yield avg
total += num
count += 1
avg = total / count
generator = moving_average()
avg1 = generator.send(10)
avg2 = generator.send(20)
print(avg1, avg2)
- yield from -> 将可迭代对象的值一个一个返回出去
def generator_fn():
a = '123'
b = 'abc'
yield from a
yield from b
generator = generator_fn()
for i in generator:
print(i)
等价于
def generator_fn():
a = '123'
b = 'abc'
for i in a:
yield i
for i in b:
yield i
generator = generator_fn()
for i in generator:
print(i)
2. 生成器推导式
- (expression for item in iterable if condition)
- (生成器所要生成的值 for 元素 in 可迭代对象 if 元素的相关条件) -> if 判断可加可不加看情况使用
generator = (i for i in range(10) if i % 2 == 0)
for i in generator:
print(i)
等价于
def generator_fn():
for i in range(10):
if i % 2 == 0:
yield i
for i in generator_fn():
print(i)
3. for 循环 和 生成器的区别
- for循环: 多个 for 循环 循环一个可迭代对象的时候,for循环会在自身内部生成一个迭代器
lis = [1, 2, 3, 4]
for i in lis:
print(i)
for i2 in lis:
print(i2)
- 生成器: 如果生成器里的值都被取过一遍以后(取完了), 那么生成器再被调用的时候里面的值是空的 -> 因为生成器和迭代器一样(生成器的本质就是迭代器)里面的值只能取一遍,取完就没有了
def generator():
yield 1
yield 2
yield 3
yield 4
r_generator = generator() # 获取生成器
i_lis = list(r_generator) # [1, 2, 3, 4] 此时,迭代器里的所有值都给了 list
i_lis2 = list(r_generator) # []
4. 生成器面试题
- 面试题一
def demo():
for i in range(4):
yield i
g = demo() # 只是获取到了生成器,生成器并没有执行
g1 = (i for i in g) # 只是获取到了生成器,生成器并没有执行
g2 = (i for i in g1) # 只是获取到了生成器,生成器并没有执行
print(list(g1)) # [0, 1, 2, 3] # 开始执行生成器 g1 而 g1 生成器是调用了 g 生成器的
print(list(g2)) # [] # 开始执行生成器 g2 而 g2 生成器是调用了 g1 生成器的 而 g1 生成器是调用了 g 生成器的,此时 g 生成器的值已经被 g1 取完了所以返回 []
- 面试题二 -> 当遇到循环执行生成器表达式的时候就将它拆开来看
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)
print(list(g)) # [20, 21, 22, 23]
拆解后的循环执行生成器表达式
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)
n = 1
# g = (add(n, i) for i in g) 此时 g = test() 拆解为一下代码
g = (add(n, i) for i in test())
n = 10
# g = (add(n, i) for i in g) 此时 g = (add(n, i) for i in test()) 拆解为一下代码
g = (add(n, i) for i in (add(n, i) for i in test()))
'''
g = (add(n, i) for i in (add(n, i) for i in test())) 拆解过程
1. (add(10, i) for i in test()) # 生成器表达式 返回 生成器->里面的值为 10, 11, 12, 13)
2. (add(10, i) for i in 生成器->里面的值为 10, 11, 12, 13) # 生成器表达式 返回 生成器->里面的值为 20, 21, 22, 23
'''
print(list(g)) # 将生成器转化为列表得到 [20, 21, 22, 23]
5. 生成器练习
- 写生成器,处理文件,用户指定要查找的文件和内容,将文件中包含要查找内容的每一行都输出到屏幕
def check_file():
with open('log.txt', encoding='utf-8') as f:
for line in f:
yield line
g = check_file()
for i in g:
print(i.strip())
- 写生成器,从文件中读取内容,在每一次读取到的内容之前加上'***'之后再返回给用户。
def check_file():
with open('log.txt', encoding='utf-8') as f:
for line in f:
yield '****' + line
g = check_file()
for i in g:
print(i.strip())