Python的生成器对象底层原理及协程思想的由来

2023-12-13 13:30:41
import inspect

frame = None

def foo():
    bar()

def bar():
    global frame
    frame = inspect.currentframe()  # 获取当前栈帧对象

foo() # 虽然函数运行完成,但是我们仍然能找到这个函数当时运行的栈帧
# 与静态静态语言不同的是,静态语言是函数调用完成栈整个销毁,而Python是放在堆上的,所以我们可以在函数调用完成后还可以去控制它,例如下面的代码

print(frame.f_code.co_name)  # 获取函数名: bar
caller_frame = frame.f_back  # 获取调用者的栈帧对象
print(caller_frame.f_code.co_name)  # 获取调用者的函数名: foo

python.exe即Python解释器会用一个叫做 PyEval_EvalFramEx 的C语言函数去执行foo函数,当执行的时候会先创建一个栈帧(stack frame)对象,Python一切皆对象,所以栈帧也是对象,然后会把代码转成字节码对象(查看字节码使用dis模块),当foo函数调用子函数bar时,又会创建一个栈帧,所有的栈帧都是分配在堆内存上的,这就决定了栈帧可以独立于调用者存在。

堆内存的特性是:不去释放它,它就会一直存在于内存当中。

在这里插入图片描述
生成器对象正是利用了 “所有的栈帧都是分配在堆内存上” 的这一特点,再外面又加了一层
在这里插入图片描述

# 从栈帧角度来剖析生成器对象的原理
import dis
def gen_func():
    yield 1
    name = "foo"
    yield 2
    age = 30
    return "bar"

gen = gen_func() # 返回生成器对象
dis.dis(gen)
# f_lasti是一个整数,表示当前指令在字节码中的偏移量,即下次运行是从字节码的多少行开始
print(gen.gi_frame.f_lasti) # -1
print(gen.gi_frame.f_locals) # {}
next(gen)   # 每一次调用都会重新生成栈帧对象
print(gen.gi_frame.f_lasti) # 2
print(gen.gi_frame.f_locals) # {}
next(gen)   # 每一次调用都会重新生成栈帧对象
print(gen.gi_frame.f_lasti) # 12
print(gen.gi_frame.f_locals) # {'name': 'foo'}

生成器对象也是分配在堆内存当中的,所以生成器对象可以独立于调用而存在。所以只要我们拿到生成器对象,每一次调用都会重新生成栈帧对象,这样我们就能控制它继续往前走。所以我们在代码的任何地方只要拿到生成器对象,都可以去恢复它、去暂停它。
正是因为这一特点,才有了协程哦!因为这就是协程的理论基础。

文章来源:https://blog.csdn.net/JENREY/article/details/134969274
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。