环境图函数父帧判定:高阶函数返回内部函数的作用域疑问
核心误区:混淆调用栈临时生命周期与词法作用域的静态绑定规则
疑惑的本质是把两套完全独立的运行机制混为一谈:
- 运行时调用栈帧:是函数被调用时临时压入调用栈的内存结构,函数执行完成return后,对应的栈帧确实会从调用栈弹出,这部分理解是正确的。
- 函数的父级作用域绑定:这是函数对象在定义阶段就被静态固定的属性,判定规则从来不会随运行时的栈状态变化——函数写在哪个代码块的词法作用域内,它的父级作用域就永远绑定到对应作用域的环境,和外层函数有没有执行完、外层栈帧还在不在调用栈上没有任何关系。
示例代码的逻辑拆解
- 内部函数
bat直接定义在bar的函数体代码块中,从词法结构上它的外层作用域就是bar的函数作用域,这个关系是Python在代码编译阶段就确定的,不会因为bar执行完返回就发生改变。 - 当
bar执行到return bat时,Python会检测到bat内部引用了bar作用域下的局部变量f,因此不会把这次bar调用产生的局部变量环境(也就是标注的f1中存储的f=foo这类数据)随栈帧弹出一起销毁,而是会把这个环境打包成闭包对象,挂载到返回的bat函数的__closure__属性上,跟着bat函数一起存活。 - 后续执行
new_func(5)时,Python会为这次bat调用创建新的临时栈帧f2,f2的父级作用域链从来不需要去当前调用栈上查找曾经的f1,而是直接读取bat定义时就绑定好的闭包环境——也就是之前bar调用时留存下来的f1对应的作用域环境。所谓f1消失,消失的只是当初压在调用栈上的临时栈帧结构,被闭包引用的局部变量环境根本没有被回收,会一直保留到bat函数对象被回收为止。
可以直接运行代码打印print(new_func.__closure__),就能看到里面存储的正是当初bar调用时传入的foo对象,这就是闭包留存外层作用域的直接证据。
内容的提问来源于stack exchange,提问作者Mikeologist
相关产品推荐
相关产品推荐

