python生成器小结

引入及概念

通过列表生成式,我们可以直接创建一个列表。但是,受内存限制,列表的容量也是有限的,当我们创建一个包含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))

输出为:

0
1
2
Traceback (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('取值完成')

输出为:

0
1
2
取值完成

取值方式二

通过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 i
def test():
    i = 0
    while i < 5:
        temp = yield i
        print(temp)
        i += 1


t = test()
# 首次调用
print(t.__next__())
# 第二次调用
print(t.__next__())
# 第三次调用
print(t.send('python'))

输出为:

0
None
1
python
2

执行流程:
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. 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,也就是说,在整个所有函数调用的参数都是第一次调用时保留的,而不是新创建的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值