编程语言中的调用堆栈实现

Call stack implementation in Programming languages

我正在学习 class 编程语言。导师正在解释调用堆栈。我怀疑导师无法正确解释。如果函数 (func1) return 是嵌套函数(比如 func2 并让 func2 使用 func1 中定义的变量)。我们将 returned 值保存在某个变量中,比如 returnedFunc。 func1 将在 func1 中的 return 语句完成后退出。 func1 的堆栈帧应该已经从调用堆栈中弹出。我们现在在代码的其他地方调用 returnedFunc。但是 returnedFunc 使用 func1 的局部变量,其堆栈帧不再存在于调用堆栈中。这是如何工作的。

示例 python 代码:

def func1():
    a = 3;
    def func2():
        print(a)
    return func2

returnedFunc = func1()
returnedFunc()

这段代码正确地打印了 3。而我期待一些垃圾值,因为调用堆栈上不再存在 func1

func2 仍然包含包含它的 scope 的副本,由编译器分配,因为它使用在该范围内找到的名称。

Python不一样。它有自己的字节码编译器、解释器和堆栈。它不为 Python 代码本身使用 C/machine 堆栈。您的示例还创建了一个新函数。它被分配为堆上的对象,而不是堆栈上的对象。所以它存在于 return 之后。

内部函数func2使用的外部函数func1的变量及其值是"packaged"和func2定义内部函数时,那"lexical environment" func2func1 returns 它。 func2 就是所谓的 closure(文章顶部给出的示例与您的示例非常相似,并对其进行了一些扩展)。你是对的,func1a 副本在该函数 returns 时从堆栈中弹出,但返回的 func2 具有 a 的绑定到 3,它将在通过 returnedFunc() 调用时使用。 Python 比将 a 绑定到即将成为垃圾的东西更聪明:)

为了说明,让我们使用一个稍微复杂一点的例子:

def outer(x):
    def inner(y):
        return x+y
    return inner

inner3 = outer(3)
inner5 = outer(5)

如您所料,

>>> inner3(1)
4
>>> inner5(1)
6

您可以使用 inspect.getclosurevars 检查闭包的绑定。请注意,每个闭包都有自己的 'x':

副本
from inspect import getclosurevars
>>> getclosurevars(inner3)
ClosureVars(nonlocals={'x': 3}, globals={}, builtins={}, unbound=set())
>>> getclosurevars(inner5)
ClosureVars(nonlocals={'x': 5}, globals={}, builtins={}, unbound=set())

但是,如果两个闭包使用相同的非局部变量(如您的示例所示),则该变量将绑定到相同的位置。考虑这种情况(来自 OP 的评论):

def func1():
    a = 3
    def func2():
        nonlocal a
        a += 1
        return a

    def func3():
        nonlocal a
        a -= 1
        return a

    return func2, func3

f2, f3 = func1()

调用函数f2f3表明它们使用相同的值a:

>>> f2(), f2(), f3(), f3()
(4, 5, 4, 3)

检查每个的 __closure__ 属性表明确实如此。 "cells"(绑定)相同,每个 "points to" 相同的 int 对象:

>>> f2.__closure__
(<cell at 0x100380fa8: int object at 0x1002739a0>,)
>>> f2.__closure__ == f3.__closure__
True

一个 cell 对象(属于 class cell)有一个 cell_contents 属性;对于 f2f3cell_contents 是 int 对象。这是两个单元格指向同一事物的另一个验证:

>>> f2.__closure__[0].cell_contents is f3.__closure__[0].cell_contents
True

其实两个cell是一样的:

>>> f2.__closure__[0] is f3.__closure__[0]
True