【问题标题】:Call stack implementation in Programming languages编程语言中的调用堆栈实现
【发布时间】:2015-12-30 07:59:09
【问题描述】:

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

示例python代码:

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

returnedFunc = func1()
returnedFunc()

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

【问题讨论】:

    标签: python callstack nested-function


    【解决方案1】:

    当定义内部函数时,内部函数func2使用的外部函数func1中的变量及其值与func2“打包”,而当定义内部函数时,“词法环境”与func2一起出现func1 返回它。 func2 就是所谓的closure (文章顶部给出的示例与您的非常相似,并对其进行了一些扩展)。你是对的,func1a 的副本在该函数返回时从堆栈中弹出,但返回的func2 具有a3 的绑定,它将在调用时使用通过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__ 属性表明情况确实如此。 “单元”(绑定)是相同的,并且每个“指向”相同的 int 对象:

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

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

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

    其实这两个单元格是一样的:

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

    【讨论】:

    • 知道了。你的意思是说封装在func2闭包中的a的副本是在堆中而不是在堆栈中。这意味着闭包存储在堆中?
    • 是的,a 的副本是 Python 对象的一部分,是堆分配的。闭包是可调用的 Python 对象,并且是堆分配的。当被调用时,调用堆栈当然会发挥作用;但那不是他们居住的地方
    • 但是当 func1 有 2 个嵌套函数 func2 和 func3 时会发生什么,这两个函数都使用 func1 的同一个本地变量。在这种情况下,a 的值不应与 func2 或 func3 的闭包“打包”,而应位于闭包之外并由 func2 和 func3 共享。不是吗?
    • 不,我的意思是,func1 有 2 个嵌套函数。 def func1 {def func2; def func3}。如果 func2 和 func3 都使用 func1 本地的同一个变量,那么它应该在闭包之外并且是一个公共内存,对吧?
    • 比地址更容易阅读:f2.__closure__ == f3.__closure__;计算结果为 True
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 2016-02-29
        • 2018-04-25
        • 2011-05-06
        • 2020-09-13
        • 2019-02-23
        • 2012-04-05
        • 2011-08-24
        • 2012-11-08
        • 2013-05-17
        相关资源
        最近更新 更多