概述
为什么会出现yield,说白了就是为了减少内存消耗,减少代码冗余。为了理解这句话,我们一步一步进行
斐波那契数
斐波那契数是一个很简单的递归数列,大家都很容易写出来这一功能
def fib(num):
n, a, b = 0, 0, 1
while n < num:
print b
a, b = b, a + b
n += 1
fib(4)
# 结果
1
1
2
3
注意到,上述代码中加入了print,这应是不好的,函数封装性很差,不能提供给第三方所用,可复用性很差
list
为提高代码的可复用性,便以返回list为好,如下
def fib(num):
n, a, b = 0, 0, 1
l=list()
while n < num:
l.append(b)
a, b = b, a + b
n += 1
return l
for i in fib(4):
print i
注意到,fib返回list便能完成函数可复用的性能,然而list却是一次性将所有数据放入内存中,进入内存的数据越多,内存占有越大。 同理列表生成式也是一样。
因此,为了控制内存开销,尽量不要使用list。
迭代版
迭代是一个访问数值的好方法,每次迭代返回下一个数值,内存占用小始终为常数,如下
class Fib(object):
def __init__(self, sum):
self.sum = sum
self.n, self.a, self.b = 0, 0, 1
def __iter__(self):
return self
def next(self):
if self.n < self.sum:
r = self.b
self.a, self.b = self.b, self.a + self.b
self.n = self.n + 1
return r
raise StopIteration()
for i in Fib(4):
print i
注意到,Fib类通过next()不断返回数列中的下一个值
yield版
def fib(num):
n, a, b = 0, 0, 1
while n < num:
yield b
a, b = b, a + b
n += 1
for i in fib(4):
# for循环只要遇到StopIteration就会停止
print i
注意到,yield版很简洁,并且也符合代码可复用行的规则,比前几个版本都要简介许多,那么yield有着怎样的功能,下面继续详细介绍
说白了,yield作用就是将一个函数变成一个生成器(python解释器视为generator,是一种仅仅拥有生成某种东西的能力,如果不用next方法是获取不到值得),不再是一个普通函数, 调用 fib(4) 不会执行 fab 函数,而是返回一个 iterable 对象,如下
print fib(4)
# 结果
<generator object fib at 0x0000000001CE5168>
具体来说,在 for 循环执行时,每次循环都会执行 fib 函数内部的代码,执行到 yield b 时,fib 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。
def fib(num):
n, a, b = 0, 0, 1
while n < num:
yield b
a, b = b, a + b
n += 1
temp = fib(4)
print temp.next()
print temp.next()
print temp.next()
print temp.next()
print temp.next()
# 结果
1
1
2
3
Traceback (most recent call last):
File "D:/workspace/MyTest/y.py", line 16, in <module>
print temp.next()
StopIteration
注意到,手动调用next()方法,便可清楚看到yield的执行流程,当函数执行完后,generator 自动抛出 StopIteration 异常,表示迭代完成,并且无需处理StopIteration 异常,循环会正常结束。
另外一种访问生成器的方法
print list(fib(4))
# 结果
[1, 1, 2, 3]
总结
- yield类似于return,只是yield返回的是一个generator。
- 当执行到这个函数时,函数内部的代码并不立马执行 ,这个函数只是返回一个生成器对象
- 当执行生成器的__next__ 的时候,代码会按照顺序去执行,当执行到yield时会返回并提出,yield后面的值就是返回值,然后记录代码执行的位置,并退出, 第二次执行的时候会根据上次代码执行的位置继续往下执行,如果__next__获取不到值的时候就会报StopIteration错误
- 生成器对象通过next和send执行,类似于上一条,而next就是封装了__next__,或是使用for迭代时,函数中的代码会执行,从开始到达 yield 关键字,然后返回 yield 后的值作为第一次迭代的返回值,当继续迭代时,便会从yield后的代码执行
需要注意的是:在生成器中,如果没有return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代
迭代器
上面说了很多东西,有一个出镜率很高的词,就是迭代器,那么什么是迭代器呢,这里再补充下知识。
简单来说,迭代器具有访问生成器的能力,可以访问到生成器的值,类似于生成器的next方法,一个一个值一个值得去迭代,只能够按照顺序的去查找
- 访问者不需要关心迭代器内部的结构,仅需通过next()方法不断去取下一个内容
- 不能随机访问集合中的某个值 ,只能从头到尾依次访问,就像列表一样
- 访问到一半时不能往回退
- 便于循环比较大的数据集合,节省内存,内存占用为常数
yield生成器类型
from inspect import isgeneratorfunction
def fib(num):
n, a, b = 0, 0, 1
while n < num:
yield b
a, b = b, a + b
n += 1
print isgeneratorfunction(fib)
print isgeneratorfunction(fib(4))
import types
print isinstance(fib, types.GeneratorType)
print isinstance(fib(4), types.GeneratorType)
from collections import Iterable
print isinstance(fib, Iterable)
print isinstance(fib(4), Iterable)
# 结果
True
False
False
True
False
True
注意到,需区分 fib 和 fib(4),fib 是一个 generator function,而 fib(4) 是调用 fib 返回的一个 generator,好比类的定义和类的实例的区别,并且fib(4)是一个可迭代的对象
yield的next和send
- next向下执行,遇到yield时返回
- send想yield发送信息,同样遇yield时返回
def func():
print '1'
a = yield 'hello'
print 'next1:'+a
print '2'
b = yield 'hi'
print 'next2:'+b
print '3'
yield 'dad'
temp = func()
a = temp.next()
print a
b = temp.send('abs')
print b
c = temp.send('qwe')
# 结果
1
hello
next1:abs
2
hi
next2:qwe
3
def f():
n=0
yield n
n+=1
yield n
n += 1
yield n
n += 1
e = f()
print e.next()
print e.next()
print e.next()
# 结果
0
1
2