Python 高级编程和异步IO并发编程 --09_4 Python是如何实现生成器的?

# 1. Python中函数的工作原理
"""

"""
def foo():
    bar()
def bar():
    pass
# python.exe 是用c语言写的,它会用一个叫做PyEval_EvalFramEx的函数(c函数)去执行foo函数。即python是在C之上运行,
# 它会首先创建一个栈帧(stack frame),这个栈帧实际上是上下文
"""
python中一切皆对象,栈帧也是对象,在栈帧的上下文里面去运行字节码。字节码是全局唯一的,
函数foo也是全局唯一的。 当foo调用子函数bar,又会创建一个栈帧,然后将函数bar的控制权
交给栈帧对象。该栈帧会运行bar这个函数的字节码;
所有的栈帧都是分配在堆内存上,堆的特性是:如果不去释放它,它就会一致存在于内存中,这
就决定了栈帧可以独立于调用者存在。即foo这个调用者不存在或退出foo这个函数也没有关系,
它仍在内存当中,只需要有指针指向这段内存,就可以对它进行控制。这个特性就决定了,可以
对函数执行过程的控制可以非常精确。
"""
import dis
print(dis.dis(foo))  # 可以查看字节码对象是什么样子的。
  6           0 LOAD_GLOBAL              0 (bar)
              2 CALL_FUNCTION            0
              4 POP_TOP
              6 LOAD_CONST               0 (None)
              8 RETURN_VALUE
None

# 首先LOAD_GLOBAL,即(bar)这个函数load进来。
# 然后调用这个bar函数CALL_FUNCTION ,是一个字节码
# POP_TOP 从栈的顶端将元素打印出来
# LOAD_CONST  由于foo函数没有return,会把(None)load进来
# RETURN_VALUE, 将None Return回去
import inspect

frame = None

def foo():
    bar()

def bar():
    global frame
    frame = inspect.currentframe()  # 将bar当前的frame赋值给全局变量

foo()
print(frame.f_code.co_name)  # bar
# 此处,即使foo函数已经退出,我们仍然可以得到栈帧里面的data
caller_frame = frame.f_back
print(caller_frame.f_code.co_name)  # foo
# 此处,虽然foo函数已经退出/运行完成,我们仍然可以得到当时运行时的栈帧

'''
静态语言的函数调用是栈的形式,函数调用完成,整个内存空间全部会销毁
动态语言的函数调用是堆的形式,函数调用完成,整个内存空间不会销毁,我们可以控制它。
'''

 生成器对象,正是利用了python函数调用的过程。利用了栈帧对象是分配在堆内存中这个特性。

import dis
def gen_func():
    yield 1
    name = "Tom-1"
    yield 2
    age = 18
    return "ABC"  # 生成器函数可以像普通函数一样,return一个值。
gen = gen_func()
print(dis.dis(gen))
'''
python解释器会编译它的字节码,遇到生成器函数定义时,会在函数体内找到yield关键词,就会知道这是一个生成器函数,不是普通函数
就会设定一个标记,来标记该函数,标记之后,当我们调用该生成器函数时,会返回一个生成器对象,该生成器对象是在PyFrameObject与
PyCodeObject基础上,封装了一层PyGenObject,即python生成器对象。代码的字节码放在PyGenObject中,PyFrameObject里面存放的是f_lasti,它指向最近执行的代码。
当我们每次对生成器进行调用时,它会运行到yield就会停止,停止之后,f_lasti就会记录最近执行的字节码位置,以及locals变量f_locals
'''
 25           0 LOAD_CONST               1 (1)
              2 YIELD_VALUE
              4 POP_TOP

 26           6 LOAD_CONST               2 ('Tom-1')
              8 STORE_FAST               0 (name)

 27          10 LOAD_CONST               3 (2)
             12 YIELD_VALUE
             14 POP_TOP

 28          16 LOAD_CONST               4 (18)
             18 STORE_FAST               1 (age)

 29          20 LOAD_CONST               5 ('ABC')
             22 RETURN_VALUE
None

#  27          10 LOAD_CONST               3 (2)  此处yield第二个值2

import dis
def gen_func():
    yield 1
    name = "Tom-1"
    yield 2
    age = 18
    return "ABC"  # 生成器函数可以像普通函数一样,return一个值。
gen = gen_func()
print(dis.dis(gen))
print(gen.gi_frame.f_lasti)    # -1   # 没有执行时的状态
print(gen.gi_frame.f_locals)   # {}    # 没有执行时的状态
next(gen)  # 通过next 调用
print(gen.gi_frame.f_lasti)    # 2   指向第1个字节码位置,数字是2,YIELD VALUE,实际上就是yield 1
print(gen.gi_frame.f_locals)   # {}
next(gen)  # 通过next再次调用
print(gen.gi_frame.f_lasti)    # 12   指向第2个字节码位置,数字是12,YIELD VALUE,实际上就是yield 2
print(gen.gi_frame.f_locals)   # {'name': 'Tom-1'}
'''
# 通过yield暂停,此处即有了控制函数暂停与继续的基础。
# 生成器对象分配在对内存中,类似于之前的栈帧,可以独立于调用者单独存在,每次调用生成器对象,就会重新生成一个栈帧对象。
只要拿到生成器对象,就可以继续控制它向前走。我们可以在任何函数/模块中,只要拿到生成器对象,都可以去恢复它,暂停它。
正是因为在任何时候都可以控制它,所以才有了后面协程的概念。
由于有了这个理论基础,利用f_lasti与f_locals两个变量,它就使得可以在生成器里面不断的循环同一个函数。由于我们以及通过
yield控制了函数的暂停,不想之前的普通函数,必须一次性运行完毕,生成器函数由于有了f_lasti就知道下一次运行应该从什么位置开始
'''

 

 25           0 LOAD_CONST               1 (1)
              2 YIELD_VALUE
              4 POP_TOP

 26           6 LOAD_CONST               2 ('Tom-1')
              8 STORE_FAST               0 (name)

 27          10 LOAD_CONST               3 (2)
             12 YIELD_VALUE
             14 POP_TOP

 28          16 LOAD_CONST               4 (18)
             18 STORE_FAST               1 (age)

 29          20 LOAD_CONST               5 ('ABC')
             22 RETURN_VALUE
None
-1
{}
2
{}
12
{'name': 'Tom-1'}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值