python生成器小结

来源:互联网 发布:网络高级工程师认证 编辑:程序博客网 时间:2024/06/08 03:41

引入及概念

通过列表生成式,我们可以直接创建一个列表。但是,受内存限制,列表的容量也是有限的,当我们创建一个包含100W个元素的列表,不仅占用内存空间比较多,而且假如我们只需要访问前几个元素,那么后面绝大部分元素占用的空间都浪费了。

如果列表元素可以按照某种算法推算出来,那我们是否可以在循环使用的过程中不断推算后续的元素呢,这样就不必创建完整的list,浪费空间。

在python中,这种一边循环一遍计算的机制,称为生成器(Generator)。

生成器创建方式

创建方式一

首先,来看一下列表的创建:

L = [x for x in range(8)]print(L)

参照上面代码,只要做如下改动,就创建一个生成器:

# 01. 将列表生成式中的[]改为()G = (x for x in range(3))print(G)

打印结果为:

<generator object <genexpr> at 0x102967ca8>

如上,就创建了一个生成器对象。
直接打印G相当于输出的是对象地址,那么该如何取其中的元素呢?

取值方式一

通过next(G)或G.next()方式进行取值

# 打印G的值,通过next()函数 获取生成器的下一个返回值print(G.__next__())print(next(G))print(next(G))print(next(G))print(next(G))

输出为:

012Traceback (most recent call last):  File "/Users/***/Project/PycharmProjects/**/generator_test.py", line 11, in <module>    print(next(G))StopIteration

可以看到,这样可以取到生成器的值,不过当角标越界后,会跑出StopIteration异常,可以通过监听次异常来完成取值。

try:    print(G.__next__())    print(next(G))    print(next(G))    print(next(G))except StopIteration as e:    print('取值完成')

输出为:

012取值完成

取值方式二

通过for循环完成取值

for x in G:    print(x)

同样可以完成对G的取值

创建方式二

只要函数中有yield关键字,该函数就不是普通函数了,而是一个生成器对象。如果再通过函数名直接调用,是无效的。

def create_num2(number):    a, b = 0, 1    for x in range(number):        yield b        a, b = b, a + b

生成器的执行流程

通过代码,看一下生成器的调用过程。

def create_num2(number):    print('create_num2 start')    a, b = 0, 1    for x in range(number):        # 遇到yield程序暂停,同时把后面的值返回        # 如果再次用next()进行调用,就会从上次yiled暂停的地方继续执行        print('....1....')        yield b        print('....2....')        a, b = b, a + b        print('....3....')    print('create_num2 stop')a = create_num2(10)print('*' * 10 + '第一次调用yiled对象' + '*' * 10)print(next(a))print('*' * 10 + '第二次调用yiled对象' + '*' * 10)print(next(a))

输出结果为:

**********第一次调用yiled对象**********create_num2 start....1....1**********第二次调用yiled对象**********....2........3........1....1

执行流程:
1. 执行 a = create_num2(10),创建生成器对象a
2. 当调用next(a)的时候,才会执行到函数中,依次打印 start …1…
3. 当执行到yield b时,会将程序暂停,同时把后面的值(b)返回
4. 接收b的返回,此时为1,so 打印1
5. 再次执行next(a)的时候,会从上次yield暂停的地方继续执行,所以先打印…2… …3…,然后再次进入for循环,打印…1…,又遇到yield,暂停中,同时返回b的值
6. 接收b的值,并打印

使用send

在调用生成器函数时,也可以对其进行传值,如下:

# 定义一个yield对象,并且增加temp = yield idef test():    i = 0    while i < 5:        temp = yield i        print(temp)        i += 1t = test()# 首次调用print(t.__next__())# 第二次调用print(t.__next__())# 第三次调用print(t.send('python'))

输出为:

0None1python2

执行流程:
1. 首次调用,因为temp = yield i为赋值语句,先执行右边代码,当执行到yield时,暂停执行,并把i返回回去,so ,打印 0
2. 第二次调用,从yield i开始,可以把yiedl i作为一个代码块,赋值给temp,此时没传值,所以打印为None,其他正常执行
3. 第三次调用,通过send方法,传递参数,这时候相当于把参数赋值给temp,所以打印结果为python,其他流程正常执行

ps:在第一次调用生成器的时候不能传参,否则会跑出异常,如下:

TypeError: can't send non-None value to a just-started generator

首次必须先调用next()或send(None)。

总结

  1. 生成器是这样一个函数,它记住上一次返回时函数体中的位置。对生成器的第二次(n)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变
  2. 生成器特点
    1. 节约内存
    2. 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,也就是说,在整个所有函数调用的参数都是第一次调用时保留的,而不是新创建的。
原创粉丝点击