Python 生成器 (通俗讲解)

一、生成器的本质

生成器是含有yield语句(或yield表达式)的函数所返回的对象。也就是说,要创建一个生成器,我们首先要定义一个函数,该函数内将使用yield表达式或yield语句来表示每次生成的值。该函数的返回值是一个生成器对象,通过把这个函数的返回值赋给一个变量,我们就得到了一个生成器对象变量,对这个对象进行send和next等操作,即可实现生成器的功能。

如下代码创建了一个最简单的生成器。

def g():
    while True:
        yield 1

a = g()

创建生成器之前,需要编写一个含有yield的函数,这是一种特殊的函数。当该函数被调用时,并不会立即执行函数中语句,而是直接返回一个生成器对象。后续的操作都可以通过生成器对象进行执行。

在这里,变量a就是一个生成器对象。

二、使用next()

next函数可用于引起生成器/迭代器的下一次生成/迭代。其返回值就是下一个生成的值。通过 next(generator) 这一方式,可以获得一系列值。

def g():
    a=1
    while True:
        yield a
        a+=1

a=g()
print(next(a))
print(next(a))
print(next(a))

'''
输出:
1
2
3
'''

由示例可见,输出结果为分别为1,2,3. 对于该过程的解释如下:

每调用一次next(a),都将执行g()函数,执行到yield处时,g()函数暂停,并将yield后的表达式作为next(a)的返回值返回。此后继续调用next(a)时,将从上一次g()函数中的暂停处继续运行,直到来到下一个yield,并执行同样的操作,依次类推。

三、StopIteration异常

在第二节中探讨的生成器采用了while True循环,这一生成器可生成的元素是无限多的,但也有只能产出有限个值的生成器,如下例所示:

def g():
    yield 1
    yield 2

a=g()
print(next(a))
print(next(a))
print(next(a))

该生成器最多产出两个元素,因此在执行最后一个(即第三个)print语句时,将会报错,引发StopIteration异常。

换言之,如果生成器中运行g()函数直到结束还没有yield出现,就会发生StopIteration异常。因此,如果在生成器对应的函数中应用return语句,也会产生同样的效果:

def g():
    if(t != 0):
        yield t
    else:
        return


a = g()

t = 1
print(next(a))
t = 0
print(next(a))  # g()在yield之前就已经return了,所以这里会报错

四、生成器函数传参

用于生成器的函数可以携带参数,此时在定义生成器对象时可以带参调用函数,如下所示:

def g(value=0,step=1):
    while True:
        yield value
        value += step


a = g(1,2)

print(next(a))
print(next(a))
print(next(a))
'''
输出:
1
3
5
'''

五、生成器方法send()

这是生成器中一个相对比较难理解的概念。笔者在初学时查阅了很多资料,百思不得其解。但通过简单的代码实验,就了解了send()的使用方法。send()函数可以接收一个参数发送(该参数可以是任意类型的,如字符串、数字、列表等)。

此时,yield作为表达式,而不是语句出现在生成器对应的函数中。

来看下面的示例:

def g():
    t = yield 1
    yield t

a=g()
print(next(a))
print(a.send("test"))
'''
输出:
1
test
'''

对于本例,可作出如下解释:

第一次print:调用next(a),g()函数执行到t=yield 1暂停(准确的来说由于赋值表达式是从右往左运算的,因此运行到yield 1这个地方就会暂停,此时还没有进行赋值操作),和上例一样,yield充当语句的角色,next()函数返回1,因此打印结果为1

第二次print:调用send("test"),此时,暂停处的 yield 1 将会充当表达式的角色,这个特殊的表达式的值为send函数的参数——即"test",然后t就被赋值为了"test",g()从该处继续运行,直到遇到下一个yield——即yield t语句,send()函数返回t的值,即打印出了test.

从这个例子当中,大家可以思考一个问题send()和next()有什么联系和区别?首先,在形式上,next是函数,可针对迭代器、生成器进行操作,所以语法是next(a)。而send()是生成器独有的方法, 语法是a.send(something)。 此外,send发送一个参数,使得暂停处的yield充当表达式的角色,并通过send()函数传入的参数决定yield表达式的值。

两者的共同点在于,他们都从上一次yield暂停处继续程序,运行到达下一个yield处,并返回该处yield后的值。

值得指出的是,初次使用生成器的时候,由于还没有“上一个yield”,所以不可以通过send()函数传入消息。因此只能使用next(a)。当然,如果非要使用send的话,传入一个None参数,即a.send(None)也是被允许的。

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Remy Huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值