生成器 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())