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
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!