生成器原理分析

生成器

  生成器是一种快速完成迭代器功能的工具,是一种特殊的迭代器。通过在函数中,设置关键字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"字符串。生成器的运行大致如我所说,通过惰性计算,每次返回一个所需的值,并且能够记住代码的执行位置,可以对整个生成器的暂停和前进进行控制。

posted @ 2019-08-27 20:32  二进制的弗洛伊德  阅读(703)  评论(0编辑  收藏  举报