生成器原理分析
生成器
生成器是一种快速完成迭代器功能的工具,是一种特殊的迭代器。通过在函数中,设置关键字yield,即为生成器函数。
def Student():
yield 1
yield 2
yield 3
为什么说生成器是一种特殊的迭代器,可以通过isinstance函数判断。
可以看到,生成器是可迭代对象,也是迭代器对象。
同样可以使用生成器来解决斐波那契数列的问题。
def gen_fib(index):
n,a,b = 0,0,1
while n<index:
yield b
a,b = b, a+b
n += 1
for data in gen_fib(10):
print (data)
原理解释
在讲生成器原理之前,我们需要先明白python函数的原理。我们的代码通过解释器Python.exe来完成,一般情况下,Python的底层由C语言编写,也就是我们常说的CPython。Python函数的执行需要借助由C语言编写好的PyEval_EvalFramEx函数,首先会创建好一个叫做stack frame的东西。stack frame(栈帧)也是一个对象,将代码会变成字节码对象。同时栈帧是放在堆内存上的。
看到这各位看官可能会觉得有点抽象。下面以一个具体的例子来说明。栈帧我们可以使用inspect模块来研究。inspect可以从实时的Python对象中获得我们想要的信息。可以看到输出结果分别为levy()和student()函数的栈帧名。
import inspect
stack_frame = None #栈帧变量
def student():
levy()
return "i am a student"
def levy():
global stack_frame #定义一个全局变量
stack_frame = inspect.currentframe() #获得当前的栈帧
student() #调用student()函数后生成栈帧
print(stack_frame.f_code.co_name) #栈帧的名字
last_stack_frame = stack_frame.f_back #上一个栈帧
print(last_stack_frame.f_code.co_name) #输出上一个栈帧的名字
通过一张图我们很容易理清其中的关系,上面为student()函数,下面为levy()函数。
借助dis模块我们可以将Python字节码反汇编成助记符,用于分析案例的原理。参考下图,LOAD_GLOBAL加载函数levy(), LOAD_CONST加载return的返回值"i am a student'。
import inspect,dis
stack_frame = None #栈帧变量
def student():
levy()
return "i am a student"
def levy():
global stack_frame #定义一个全局变量
stack_frame = inspect.currentframe() #获得当前的栈帧
dis.dis(student) #dis模块的使用
如果理解了上面的原理的话,我们下面再来看生成器的原理分析。生成器中的堆内存存储了GenObject指向了帧对象和字节码对象。
生成器原理的示例如下,我们输出反编译后的助记符行号以及本地存储变量(字典形式),通过不断的调用next()函数,来顺序访问生成器函数中的对象。
def stu():
yield "hello"
student = 'levy'
yield "world"
return "i am a student"
import dis
xiaoming = stu()
dis.dis(xiaoming)
print(xiaoming.gi_frame.f_lasti)#字节码对应行数
print(xiaoming.gi_frame.f_locals)#本地变量字典形式
next(xiaoming)#执行下一个
print(xiaoming.gi_frame.f_lasti)
print(xiaoming.gi_frame.f_locals)
next(xiaoming)
print(xiaoming.gi_frame.f_lasti)
print(xiaoming.gi_frame.f_locals)
-1表示生成器函数还未开始执行,存储的变量为空{},2表示执行到第二行YIELD_VALUE返回了一个"hello"字符串,同理12表示第十二行YIELD_VALUE返回了一个"world"字符串。生成器的运行大致如我所说,通过惰性计算,每次返回一个所需的值,并且能够记住代码的执行位置,可以对整个生成器的暂停和前进进行控制。