Python源码阅读-闭包的实现

闭包

e.g.

需要回答, 什么是闭包, CPython底层是如何实现的?

PyCodeObject

我们关注两个, co_freevarsco_cellvars

对于我们上面的那个示例, add是外层函数, do_add是嵌套函数, 我们可以通过func_code打印看看

此时图示

这时候, 只是记录了使用到的变量名, 标记下是否使用了外层的/被内层使用的变量

具体的值是在运行时确定的, 例如

此时x=5, 这个是在add的名字空间里面的, 那么, x=5是怎么传递到嵌套函数内? 嵌套函数又是如何知晓x的值?

记住这两个问题, 然后我们首先来看一个新的数据结构

PyCellObject

这是个很简单的基本对象, 有一个ob_ref指向另一个PyObject, 仅此而已

图示

作用呢?

值的确认与传递过程

调用

此时, 开始调用函数

逻辑即, 如果发现当前函数co_cellvars非空, 即表示存在被内层函数调用的变量, 那么遍历这个co_cellvars集合, 拿到集合中每个变量名在当前名字空间中的值, 然后放到当前函数的f->f_localsplus中.

这里, 我们可以知道x=5被放进去了

为什么放到f->f_localsplus中呢?

看看PyFrameObject

注意f_localsplus

创建过程

call_function的时候, new了一个PyFrameObject

原因: 因为函数中的局部变量总是固定不变的, 在编译时就能确定局部变量使用的内存空间的位置, 也能确定访问局部变量的字节码应该如何访问内存, 有了这些信息, Python就能借助静态的方法实现局部变量, 而不是动态查找PyDictObject, 提高执行效率

示例函数的f_localsplus

看一下上面赋值用的宏定义

最终得到

接下去呢? CALL_FUNCTION最后怎么处理将cell传入嵌套函数?

传递

CALL_FUNCTION 完成new一个PyFrameObject之后,

最终执行这个frame

PyEval_EvalFrameEx

查看一下dis的结果

首先LOAD_CLOSURE 0

然后, BUILD_TUPLE, 将cell对象打包成tuple, 得到('x', )

然后, 开始, 载入嵌套函数do_add, 入栈

调用MAKE_CLOSURE

来关注一下 PyFunction_SetClosure

do_addPyFunctionObjectfunc_closure指向一个tuple

注意: 这时候, 外层变量已经固定下来了!!!!!!

然后, 在嵌套函数被调用的时候

看下PyFunction_GET_CLOSURE

然后, 进入 PyEval_EvalCodeEx, 注意这里的closure参数即上一步取出来的func_closure, 即外层函数传进来的tuple

最后, 再来看一个闭包的dis

注意BUILD_TUPLE

dis结果

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

2 2 收藏 评论

关于作者:wklken

Pythonista/vimer 个人主页 · 我的文章 · 37 ·   

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部