编程语言中的调用堆栈实现
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" func2
当 func1
returns 它。 func2
就是所谓的 closure(文章顶部给出的示例与您的示例非常相似,并对其进行了一些扩展)。你是对的,func1
的 a
副本在该函数 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()
调用函数f2
和f3
表明它们使用相同的值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
属性;对于 f2
和 f3
,cell_contents
是 int 对象。这是两个单元格指向同一事物的另一个验证:
>>> f2.__closure__[0].cell_contents is f3.__closure__[0].cell_contents
True
其实两个cell是一样的:
>>> f2.__closure__[0] is f3.__closure__[0]
True
我正在学习 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" func2
当 func1
returns 它。 func2
就是所谓的 closure(文章顶部给出的示例与您的示例非常相似,并对其进行了一些扩展)。你是对的,func1
的 a
副本在该函数 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()
调用函数f2
和f3
表明它们使用相同的值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
属性;对于 f2
和 f3
,cell_contents
是 int 对象。这是两个单元格指向同一事物的另一个验证:
>>> f2.__closure__[0].cell_contents is f3.__closure__[0].cell_contents
True
其实两个cell是一样的:
>>> f2.__closure__[0] is f3.__closure__[0]
True