【问题标题】:How call arguments stack works in Python?调用参数堆栈如何在 Python 中工作?
【发布时间】:2023-03-04 14:27:01
【问题描述】:

我正在学习 python 并试图了解堆栈在 python 中是如何工作的。我有些疑惑。我知道堆栈是如何工作的,并且我已经阅读了很多关于它的文章和教程。我在stackoverflow上阅读了很多帖子,它们都很好,但我仍然有些疑问。

到目前为止,我已经阅读了堆栈存储和函数的返回值。它由 LIFO 负责人工作。

只有堆栈的第一个值会返回并存储在顶部。

所以我的问题是 - 假设有四个变量:

a = 9
b = 2
c = 13
d = 4

它们按函数调用值的顺序存储在堆栈中,例如:

sum = a + d
sum = b + c

a = 9
d = 4
b = 2
c = 13

他们会从上到下返回。现在我的困惑是-如果任何操作需要使用dc,而堆栈从顶部返回值,堆栈将如何获取值dc,只要它们在堆栈中间. stack会先返回a再返回d吗??

这就是混乱。

【问题讨论】:

  • 访问堆栈变量时不涉及pushing 或poping。
  • 我很困惑,你的意思是“堆栈”作为数据结构还是“堆栈”作为内存?如果是第一种情况 - 如果您需要的值不是来自顶部,请不要使用它,如果是后者,则变量位于堆叠函数的“虚拟”堆内,所以没关系
  • 你为什么用 [stack-overflow] 标记这个?
  • 你试过在解释器中运行一些代码吗?这不是它的工作原理。

标签: python stack callstack


【解决方案1】:

如果您对 python 在底层的工作原理感兴趣,可以使用标准 CPython 库 dis。 这是您的测试代码的输出:

>>> import dis
>>> def test():
...     a = 9
...     b = 2
...     c = 13
...     d = 14
...     sum1 = a + d
...     sum2 = b + c
... 
>>> dis.dis(test)
  2           0 LOAD_CONST               1 (9)
              3 STORE_FAST               0 (a)

  3           6 LOAD_CONST               2 (2)
              9 STORE_FAST               1 (b)

  4          12 LOAD_CONST               3 (13)
             15 STORE_FAST               2 (c)

  5          18 LOAD_CONST               4 (14)
             21 STORE_FAST               3 (d)

  6          24 LOAD_FAST                0 (a)
             27 LOAD_FAST                3 (d)
             30 BINARY_ADD          
             31 STORE_FAST               4 (sum1)

  7          34 LOAD_FAST                1 (b)
             37 LOAD_FAST                2 (c)
             40 BINARY_ADD          
             41 STORE_FAST               5 (sum2)
             44 LOAD_CONST               0 (None)
             47 RETURN_VALUE        

如您所见,它与堆栈无关。 即使调整后的代码也不会这样做,比如

>>> def test2(a,b,c,d):
...     sum1 = a + d
...     sum2 = b + c
... 
>>> dis.dis(test2)
  2           0 LOAD_FAST                0 (a)
              3 LOAD_FAST                3 (d)
              6 BINARY_ADD          
              7 STORE_FAST               4 (sum1)

  3          10 LOAD_FAST                1 (b)
             13 LOAD_FAST                2 (c)
             16 BINARY_ADD          
             17 STORE_FAST               5 (sum2)
             20 LOAD_CONST               0 (None)
             23 RETURN_VALUE        

如果您对计算中的一般堆栈概念感兴趣,您应该切换到一些低级(2.5 代语言,如 C),或者更深入地学习汇编语言。

阅读各种调用约定可能也是另一个不错的起点(例如x86 calling conventions

【讨论】:

  • 它的参考价值?像 LOAD_FAST 调用 0(a) 那么是 0 内存地址?
  • 它是 Python 栈帧中变量的索引。尽管它被称为 stack 框架,但您应该将其视为 x86/ARM 通用寄存器(即 eax、ebx / r0、r1、r2 等)。帧在 CPython 虚拟机的堆栈中分配(因此 堆栈帧),但数据不按 LIFO 顺序访问。这是 CPython 的 bytecodes reference
  • 当 python 变量(不是一些 const 值)被压入堆栈时,是的,你可以说它是通过引用访问的。
【解决方案2】:

虽然堆栈作为后进先出法工作,但您必须小心哪些数据结构正在被堆栈。例如,许多编程语言在调用函数时使用堆栈来存储参数和局部变量。但堆叠的是整个调用框架,而不是框架中的单个变量。

例如,在 C 中,我可能有局部变量 a、b、c、d “在堆栈上”,但这意味着它们存储在距帧开始的已知固定偏移处。变量可以按任何顺序访问,并且它们不会四处移动(除了编译器可能正在做的优化)。

Python 让它变得更复杂一些。至少在 CPython 中,幕后有一个堆栈框架,但您看到的局部变量实际上存储在一个函数实例对象上的一个数组中(参见下面的 shadowranger 注释),该实例对象构成函数的本地命名空间。任何时候都可以通过dict访问所有变量。

在这些情况下,局部变量本身不是 LIFO,只要您知道它们在命名空间字典中的偏移量或名称,就可以任意访问。

【讨论】:

  • 局部变量实际上并没有自动存储在dict 中。 locals()(可能还有闭包嵌套访问?)必须根据需要创建一个dict,如果您从不调用它,则局部变量存储在一个数组中。如果你使用dis,你会看到不同;使用 LOAD_FAST/STORE_FAST 访问局部变量,它在一个按数字索引的数组中查找(数组的大小由编译器确定,名称分配给数字)。使用dict 进行本地访问太慢了。
  • @ShadowRanger - 谢谢!我更新了参考您的评论。我总是假设本地人和类实例方法查找大致相同。
猜你喜欢
  • 2014-08-30
  • 2020-03-26
  • 2013-02-16
  • 2020-02-12
  • 1970-01-01
  • 1970-01-01
  • 2011-06-01
  • 2021-10-23
  • 2011-02-10
相关资源
最近更新 更多