1 生成器

1.1 初识生成器

什么是生成器?这个概念比较模糊,各种文献都有不同的理解,但是核心基本相同。生成器的本质就是迭代器,在python社区中,大多数时候都把迭代器和生成器是做同一个概念。不是相同么?为什么还要创建生成器?生成器和迭代器也有不同,唯一的不同就是:迭代器都是Python给你提供的已经写好的工具或者通过数据转化得来的,(比如文件句柄,iter([1,2,3])。生成器是需要我们自己用python代码构建的工具。最大的区别也就如此了。

1.2 生成器的构建方式

在python中有三种方式来创建生成器:

  1. 通过生成器函数

  2. 通过生成器推导式

  3. python内置函数或者模块提供(其实1,3两种本质上差不多,都是通过函数的形式生成,只不过1是自己写的生成器函数,3是python提供的生成器函数而已)

1.3 生成器函数

我们先来研究通过生成器函数构建生成器。

首先,我们先看一个很简单的函数:

def func():
​
    print(11)
​
    return 22
​
ret = func()
​
print(ret)
​
# 运行结果:
1122

将函数中的return换成yield,这样func就不是函数了,而是一个生成器函数

def func():
    print(11)
    yield 22

我们这样写没有任何的变化,这是为什么呢? 我们来看看函数名加括号获取到的是什么?

def func():
​
    print(11)
​
    yield 22
​
ret = func()
​
print(ret)
​
# 运行结果:
<generator object func at 0x000001A575163888>

运行的结果和最上面的不一样,为什么呢?? 由于函数中存在yield,那么这个函数就是一个生成器函数.

我们在执行这个函数的时候.就不再是函数的执行了.而是获取这个生成器对象,那么生成器对象如何取值呢?

之前我们说了,生成器的本质就是迭代器.迭代器如何取值,生成器就如何取值。所以我们可以直接执行next()来执行以下生成器

def func():
​
     print("111")
​
     yield 222
​
gener = func() # 这个时候函数不会执⾏. ⽽是获取到⽣成器
​
ret = gener.__next__() # 这个时候函数才会执⾏
print(ret)  # 并且yield会将func生产出来的数据 222 给了 ret。  
​
结果:
​
111222

并且我的生成器函数中可以写多个yield。

def func():
​
    print("111")
​
    yield 222print("333")
​
    yield 444
​
gener = func()
​
ret = gener.__next__()
​
print(ret)
​
ret2 = gener.__next__()
​
print(ret2)
​
ret3 = gener.__next__()
​
# 最后⼀个yield执⾏完毕. 再次__next__()程序报错
print(ret3)
​
​
​
结果:
​
111222333444

当程序运行完最后一个yield,那么后面继续运行next()程序会报错,一个yield对应一个next,next超过yield数量,就会报错,与迭代器一样。

    yield与return的区别:

        return一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值。

        yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素。

举例:

我们来看一下这个需求:老男孩向楼下卖包子的老板订购了10000个包子.包子铺老板非常实在,一下就全部都做出来了  

def eat():
​
    lst = []
​
    for i in range(1,10000):
​
        lst.append('包子'+str(i))
​
    return lst
​
e = eat()
​
print(e)

这样做没有问题,但是我们由于学生没有那么多,只吃了2000个左右,剩下的8000个,就只能占着一定的空间,放在一边了。如果包子铺老板效率够高,我吃一个包子,你做一个包子,那么这就不会占用太多空间存储了,完美。

def eat():
​
    for i in range(1,10000):
​
        yield '包子'+str(i)
​
e = eat()
​
for i in range(200):
    next(e)

这两者的区别:

    第一种是直接把包子全部做出来,占用内存。

    第二种是吃一个生产一个,非常的节省内存,而且还可以保留上次的位置。

def eat():
​
    for i in range(1,10000):
​
        yield '包子'+str(i)
​
e = eat()
​
for i in range(200):
    next(e)
    
for i in range(300):
    next(e)
# 多次next包子的号码是按照顺序记录的。

1.4 send 方法(了解,不讲)

·接下来我们再来认识一个新的东西,send方法

# next只能获取yield生成的值,但是不能传递值。
def gen(name):
    print(f'{name} ready to eat')
    while 1:
        food = yield
        print(f'{name} start to eat {food}')
​
dog = gen('alex')
next(dog)
next(dog)
next(dog)
​
​
# 而使用send这个方法是可以的。
def gen(name):
    print(f'{name} ready to eat')
    while 1:
        food = yield 222
        print(f'{name} start to eat {food}')
​
dog = gen('alex')
next(dog)  # 第一次必须用next让指针停留在第一个yield后面
# 与next一样,可以获取到yield的值
ret = dog.send('骨头')
print(ret)
​
​
def gen(name):
    print(f'{name} ready to eat')
    while 1:
        food = yield
        print(f'{name} start to eat {food}')
​
dog = gen('alex')
next(dog)
# 还可以给上一个yield发送值
dog.send('骨头')
dog.send('狗粮')
dog.send('香肠')
View Code

相关文章: