生成器:yield语句的使用

生成器

生成器可以理解为用于生成列表、元组等可迭代对象的机器。既然是机器,没启动之前,在Python中只是一个符号。也就是说,生成器还不是实际意义上的列表,因此比列表更加节省内存空间。基于yield语句,生成器可以暂停方法并返回一个中间结果。该方法会保存执行上下文,需要使用时可以恢复。

举个经典的使用yield的栗子

斐波那契数列可以用生成器的方法来实现。

def fibonacci():
    a,b = 0,1
    while True:
        yield b
        a,b = b,a+b

然后我们可以使用next()方法和for循环从生成器中获取到新的元素,就像迭代器。

fib = fibonacci()
print(next(fib))
print(next(fib))
print(next(fib))
print(next(fib))
[print(next(fib)) for i in range(10)]

然后看一下从控制台获得的结果:
在这里插入图片描述
从结果中我们可以看见我总共输出了14次,正好对应了14个结果,看起来像遍历输出了一个列表。

但是这个函数返回的是一个generator对象,是一种特殊的迭代器,它可以被无限次的调用,每次都会生成序列中的下一个元素(通过上面的例子也可以看出这一点),而且语法简洁,可无限调用的性质也没有影响代码的可读性。

通过上面的例子我们还可以得出一个结论:

一个带有yield的函数就是一个generator,这个generator与普通的函数不同,虽然生成它的过程和正常的函数调用时一样的(例如:fib = fibonacci()),但不会执行任何函数代码直到调用了next(),执行过程与正常的函数相同,但是每到yield就像终止了并返回一个迭代值,过程看起来就像函数被终止了数次,每次都会返回当前的迭代值。

好处显而易见,yield会直接让函数获得迭代的能力,对比把类实例化保存状态来获得next()的值,不仅代码简洁,整个过程也更加清晰。

什么时候用到yield

每次当需要返回一个序列的函数时候或在循环中运行的函数时,都应该考虑使用生成器。当元素被传递到另一个函数进行处理时,一次返回一个函数可以提高整体的性能。

再举个栗子

在下面的示例中,每个函数都定义了一个对序列的转换,然后将这些函数全都链接起来并使用,每次调用都处理一个元素并返回其结果。

def power(values):
    for value in values:
        print('power is %s'%value)
        yield value

def adder(values):
    for value in values:
        print('adder to %s'%value)
        if value%2==0:
            yield value+3
        else:
            yield value+2
list = [1,4,7,9,12,19]
results = adder(power(list))
print(next(results))
print(next(results))
print(next(results))
print(next(results))

控制台输出结果:
在这里插入图片描述
在这个例子里面可以看到我使用了多个处理序列值得可迭代函数,这样保证了函数不复杂,同时可以计算出整个集合的结果。

生成器的另一个重要特性

就是可以利用next函数与调用的代码进行交互。yield变成了一个表达式,而值可以通过名为send的新方法传递:

def QA():
    print('请说出你的问题')
    while True:
        answer = (yield)
        if answer is not None:
            if answer.endswith("?"):
                print('请说出更多的问题')
            elif '好' in answer:
                print("you are good")
            elif '坏' in answer:
                print("you are bad")
qa = QA()
print(next(qa))
qa.send('我不太好啊')
print(next(qa))
qa.send('你玩具是不是坏了')
print(next(qa))
qa.send('你现在感觉怎么样?')
print(next(qa))

在这里插入图片描述
可以从这个例子中看出来send与next的作用类似,但会将函数定义内部传入的值变成yield的返回值,因此这个函数可以根据客户端的代码来改变自身行为,就像通过send传入不同的内容,输出结果也不同一个道理。为了完成这个行为还添加了两个函数:throw和close。他们可以向生成器抛出错误。
throw:允许任何一种类型的异常。
close:作用相同,但是必定会引发另一个特定的异常-GeneratorExit。在这种情况下生成器函数必须再次处理GeneratorExit或者StopIteration。

yield与print、return的区别

def func1():
    for i in range(1, 5):
        print (i)

def func2():
    for i in range(1, 5):
        return i

def func3():
    for i in range(1, 5):
        yield i

func1()
print (func2())
print (func3())

在这里插入图片描述
可以看出来第一个print在调用这个函数的时候就输出了整个列表;而return则只返回了第一个数就终止了函数的执行;yield则是输出了一个生成器对象,因为yield函数需要next()来调用启动。

本文内容参考于“Python高级编程(第二版)”中的2.2.2 yield语句

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值