生成器: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语句