大家好,小编来为大家解答以下问题,python生成器的应用场景,python生成器有几种写法,今天让我们一起来看看吧!

python的生成器hanshu中的from怎么使用_linux

Source code download: 本文相关源码



1. 什么是生成器?

  • 普通函数:返回一个值给调用者,把值返回给调用者以后,这个函数就死掉了,也就是被销毁了。
  • 生成器函数:yield(“生出”) 一个值给调用者,yield(“生出”)了一个值以后,函数还活着,调用者有需要的时候会接着生第二个值、第三个值、第四个值python画树状图。。。

明明是生成器,为什么要提到函数呢?这是因为大多数时候生成器是以函数来实现的。

编程源于生活:神奇的包子铺

楼下王大爷开了一件包子铺,你可不要小瞧这件包子铺,这件包子铺有两个神奇的蒸笼,只要把蒸笼放在蒸架上就能自己产生包子。

小A跟小B同时去吃包子。小B点了50个包子,王大爷就使用神奇的蒸笼一下子给了小B蒸了50个,并且这50个包子使用了50个小碗来装,装完以后,王大爷就把蒸架撤下了,于是小B开始坐下吃包子。

小A也买了50个包子,但是他跟王大爷说,你把我的包子放在蒸笼里面,我每次只吃一个。于是王大爷给了小A一个小碗,小A每吃完一个包子,就去蒸笼里面拿一个包子,蒸笼被小A打开的时候,就产生了一个包子给他。

在这里面:

  • 小A:生成器函数调用者
  • 小B:普通函数的调用者
  • 小A的蒸笼:生成器函数(小A拿了一个包子以后,继续放在蒸架上准备剩下的49个包子,函数保留状态,可以记录已经拿了几个,还剩下几个)
  • 小B的蒸笼:普通函数(给小B拿了50包子,直接就被王大爷收起来了,函数被销毁,还想吃包子就需要王大爷把蒸笼重新放上蒸架)
  • 小A用1个小碗吃:占用内存大小(1KB)
  • 小B用50个小碗吃:占用内存大小(50KB)
  • 王大爷:CPU
def simple_generator():
    x=1
    yield

genrator = simple_generator()  # 函数内使用yield关键字,会返回一个生成器对象
print(type(genrator))  # <class 'generator'>

老样子,看看生成器对象里面有什么干货

print(dir(genrator))
[..., '__iter__', '__next__'...]  # 又看到了我们的老朋友。。。迭代器里面的两个兄弟

这里我们能得到什么结论呢?

生成器也是一个迭代器,它具备迭代器的功能。生成器是一个特殊的迭代器

不熟悉迭代器的朋友可以看看迭代器的内容

2. 创造生成器

2.1 通过yield关键字

def simple_generator():
    x=1
    yield x  # 第一次调用next(),执行到这里就停下,返回x

genrator = simple_generator()

print(genrator)   
# <generator object simple_generator at 0x7f9e02077660>

print(type(genrator))  
# <class 'generator'>

如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个生成器(generator)

2.2 生成器表达式

generator = (i for i in range(10))
print(generator)

列表推导式的 [ ] 改成 ( )就可以创建一个生成器

那生成器跟列表有什么不同的呢?来举一个非常直观的例子

_list = [i for i in range(10)]

print("取出第一个包子:",_list[0])  # 取出第一个包子:0
print("取出第二个包子:",_list[1])  # 取出第二个包子:1
print("取出第三个包子:",_list[2])  # 取出第三个包子:2

for i in _list:
    print("取出包子序号:",i)

取出包子序号: 0
取出包子序号: 1
取出包子序号: 2
取出包子序号: 3
取出包子序号: 4
取出包子序号: 5
取出包子序号: 6
取出包子序号: 7
取出包子序号: 8
取出包子序号: 9
generator = (i for i in range(10))

print("取出第一个包子:",next(generator))  # 取出第一个包子:0
print("取出第二个包子:",next(generator))  # 取出第二个包子:1
print("取出第三个包子:",next(generator))  # 取出第三个包子:2

for i in generator:
    print("取出包子序号:",i)

取出包子序号: 3   # 这里跟列表有点不一样,列表每次都从0开始,而生成器只能从当前已经拿到的数开始
取出包子序号: 4
取出包子序号: 5
取出包子序号: 6
取出包子序号: 7
取出包子序号: 8
取出包子序号: 9

对比上面的包子铺:

小B一次性拿到了50个包子,每个包子放在一个碗里面,假设给碗编号,那么小B可以通过编号任意拿一个包子,小B可以随意给包子排列组合,小B还喜欢数包子,小B就一直数自己有多少个包子,反反复复数都可以。

但是小A的情况就不一样了。他只有一个碗,一次只能装一个,拿了1号包子以后,要想拿2号包子,就只能把1号包子丢掉或者吃掉。小A还不能数包子,他只能记录自己已经拿了几个包子

  • 小B通过编号任意拿包子:列表索引取值
  • 小A拿完1号包子再拿二号:通过next()函数取值
  • 小A拿完2号就再也不能拿一号(一号已经被丢掉/吃掉):生成器只能执行一次
  • 小B数包子可以重复多次,并且每次都能从1号开始数:for ... in ... 可以多次,每次都可以从索引为0开始
  • 小A只能从当前拿到的包子开始数,一旦数完就再也没法数:for ... in ... 只能一次,当前拿到第几个数,就从这个数开始遍历

结论:生成器保存的是算法,每次调用next(),就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

3. yield关键字

yield这个关键字是一个比较抽象的概念。

还是包子铺:

王大爷把蒸笼放在蒸架上开始蒸包子,小A每次打开蒸笼盖子,蒸笼会当场捏一个蒸熟了的包子给他,并且自动关上蒸笼盖。

  • 王大爷:CPU
  • 蒸笼:生成器函数
  • 蒸架:内存空间
  • 蒸笼上架:加载函数
  • 打开蒸笼盖子:执行next()方法
  • 将包子给小A:yield(生成)了一个值给调用者
  • 关上蒸笼盖:函数退出(也可以理解为暂停)
  • 下一次打开盖子:又执行next()方法,从上一次yield的地方开始执行,遇到下一个yield又退出
def make_baozi(xx):
    return xx

def simple_generator():
    print("第一次制作猪肉白菜馅的包子")
    formulation = "猪肉、白菜"
    x_zhu = make_baozi(formulation)
    yield x_zhu    
    # 第一次开盖子,做好猪肉白菜包子返回给你。暂停,等你吃完,并且记录我已经把猪肉白菜包子给你了
    # 下一次执行上面这块将不会再执行了,而是从这个关键字往后开始执行

    print("第二次制作叉烧馅的包子")
    formulation = "叉烧"
    x_cha = make_baozi(formulation)
    yield x_cha  # 第二次开盖子,做好叉烧包子返回给你,暂停,等你吃完,并且记录我已经把猪肉白菜包子、叉烧包子给你了

    print("第三次制作玉米馅的包子")
    formulation = "玉米"
    x_yu = make_baozi(formulation)
    yield x_yu  # 第一次开盖子,做好玉米包子返回给你,暂停,等你吃完,并且记录我已经把猪肉白菜包子、叉烧包子、玉米包子给你了


genrator = simple_generator()

print(next(genrator))
# 第一次拿猪肉白菜馅的包子
# 猪肉、白菜

print(next(genrator))
# 第二次拿叉烧馅的包子
# 叉烧

print(next(genrator)) 
# 第三次拿玉米馅的包子
# 玉米

print(next(genrator))
Traceback (most recent call last):
  File "/app/util-python/test.py", line 36, in <module>
    print(next(genrator))
StopIteration

我们可以把yield理解成为函数的暂停键,next()函数是开始键。

暂停的同时,也会将值返回给你。等下一次开始的时候,就从上一次暂停的地方继续执行。

3.1 yield from

Python3.3版本的PEP 380中添加了yield from语法。yield from 可以直接把可迭代对象中的每一个数据作为生成器的结果进行返回

def simple_generator():
    a = [1,2,3]
    yield a

genrator = simple_generator()
print(genrator.__next__())
# [1,2.3]
def simple_generator():
    a = [1,2,3]
    yield from a

genrator = simple_generator()
print(genrator.__next__())  # 1
print(genrator.__next__())  # 2
print(genrator.__next__())  # 3

4. 生成器方法

生成器是迭代器的一种,生成器比迭代器多了三种方法:send()close()throw()

4.1 send

当生成器处于暂停状态时,向生成器传一个值

def simple_generator():
    a = "测试"
    a = yield a
    yield a

genrator = simple_generator()
print(genrator.send("dd"))
Traceback (most recent call last):
  File "/app/util-python/test.py", line 12, in <module>
    genrator.send("dd")
TypeError: can't send non-None value to a just-started generator

上面的用法报错了,为什么呢?因为此时我们的生成器还没有启动,我们需要先启动生成器。

启动生成器的方法1:

print(genrator.send(None))

启动生成器的方法2:

print(genrator.__next__())

生成器启动后,再尝试一下:

def simple_generator():
    a = "测试"
    
    # 第一次启动会执行到这里,暂停后,可以通过send向这个关键字这里传参
    a = yield a
    print("下次执行的代码块")
    yield a

genrator = simple_generator()
print(genrator.__next__())   # 打印:测试
print(genrator.send("new value"))  # 打印:new value

总结一下:这个方法可以向生成器发送一个参数,但是生成器必须先启动,也就是必须先执行到第一个 yield 关键字的地方,然后暂停在这个关键字这。此时按下暂停键的这个 yield 就可以接受外部send的值的

4.2 throw()

在生成器函数执行暂停处,抛出一个指定的异常

def simple_generator():
    a = "开始执行"
    try:
        yield a
    except ValueError:
        print("捕获到了抛进来的异常")

    b = "执行第二个yield"
    yield b

genrator = simple_generator()
print(genrator.__next__())   
# 执行到 yield a 处,所以这里应该是打印:开始执行

print(genrator.throw(ValueError))  
# 从 yield a 处往下开始执行,抛出一个 ValueError 异常,如果抛出的异常被处理掉,那么就会接着往下执行到  yield b 处,否则直接抛出异常,程序停止
# 所以此处的结果应该打印:执行第二个yield

可以跟下面的代码对比这着看看,应该能加深理解

def simple_generator():
    a = "开始执行"
    try:
        yield a
        raise ValueError
    except ValueError:
        print("捕获到了抛进来的异常")

    b = "即将准备执行第二个yield"
    yield b

genrator = simple_generator()
print(genrator.__next__())   
# 执行到 yield a 处,所以这里应该是打印:开始执行

print(genrator.__next__())  
# 从 yield a 处往下开始执行,执行 raise ValueError抛出一个 ValueError 异常,紧接着执行到 yield b 处

4.3 close()

向生成器抛出一个GeneratorExit异常,意味着生成器生命周期结束。

def simple_generator():
    a = "开始执行"

    try:
        yield a
    except ValueError:
        print("捕获到了抛进来的异常")
    
    except GeneratorExit:
        print("生成器退出")

    b = "执行第二个yield"
    yield b


genrator = simple_generator()
print(genrator.__next__())
genrator.close()

上面这段代码最终结果:

Traceback (most recent call last):
  File "/app/util-python/test.py", line 23, in <module>
    genrator.close()
RuntimeError: generator ignored GeneratorExit

因为生成器已经执行了genrator.close()方法,抛出了了GeneratorExit异常,生成器方法后续执行的语句中,不能再有yield语句,否则会产生 RuntimeError

所以这里需要下面两行代码去掉。或者先执行一次 genrator.__next__()再执行genrator.close(),让函数将所有的 yield 执行完再 close

b = "执行第二个yield"
yield b

5. 实现斐波拉契数列(Fibonacci)

def fib(max):
    n = 0
    a = 0
    b = 1
    while n < max:
        yield b
        a, b = b, a+b
        n+=1


r = fib(10)
for i in r:
    print(i)
1
1
2
3
5
8
13
21
34
55