python中的函数、生成器的工作原理
1.python中函数的工作原理def foo(): bar() def bar(): pass python的解释器,也就是python.exe(c编写)会用PyEval_EvalFramEx(c函数)运行foo()函数 import dis print(dis.dis(foo)) #打印字节码 ? 可以尝试着去打印foo的字节码: 关于字节码的解释: LOAD_GLOBAL:首先导入bar这个函数 打印bar的字节码: print(dis.dis(bar))
这个字节码全局是唯一的,函数是全局唯一的,然后在函数里面会调用另外一个函数。 import inspect frame = None #保存frame def foo(): bar() def bar(): global frame #引入全局变量 frame = inspect.currentframe() #将bar的frame赋给全局变量 foo() print(frame.f_code.co_name) #bar 函数退出之后,依然可以拿到bar函数的栈帧 caller_frame = frame.f_back print(caller_frame.f_code.co_name) #foo 也可以拿到foo函数的栈帧 ? 2.生成器的实现原理在静态语言中,函数调用的时候是一个栈的形式,函数调用完成之后栈就会被销毁。 PyEval_evalFrameEx会创建一个foo的栈帧对象,这个对象里面有两个属性。f_back为None,因为没有上层函数,f_code指向foo的字节码 def gen_func(): yield 1 name = "ming" yield 2 age = 28 return "kebi" #在早期的生成器版本中不能使用return 当python解释器在读取gen_fun()这个函数的时候,发现yield关键字就会将其标记为生成器函数。 ? ? 在PyFrameObject和PyCodeObject上面又封装了一层PyGenObject,就是python的生成器对象。 def gen_func(): yield 1 name = "ming" yield 2 age = 28 return "kebi" #在早期的生成器版本中不能使用return import dis gen = gen_func() print(dis.dis(gen)) ?查看结果: 这里面可以看到有两次yield。当我们每一次对生成器做一次调用的时候,它遇到yield就会停止。 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‘:‘ming‘} 与上方字节码是一样的。 这样整个生成器对象就存在与堆内存中,可以独立存在,每次执行一次函数,就会生成一个栈帧对象。 ? 3.pyc文件当你在执行python代码的时候,会发现执行目录下面会出现.pyc文件。 [[email?protected] resources]# ls r1.py r1.pyc r2.py r3.py [[email?protected] resources]# cat r1.pyc ?:][email?protected] dZdS(tname_r1N(R(((s/tmp/demo/resources/r1.py<module>s r1.pyc是一个二进制文件,当执行的文件中存在包的引入就会编译生成二进制文件。 当python程序运行时,编译的结果则是保存在位于内存中的PyCodeObject中,当Python程序运行结束时,Python解释器则将PyCodeObject写回到pyc文件中。 当python程序第二次运行时,首先程序会在硬盘中寻找pyc文件,如果找到,则直接载入,否则就重复上面的过程。 所以我们应该这样来定位PyCodeObject和pyc文件,我们说pyc文件其实是PyCodeObject的一种持久化保存方式。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |