为啥对 Python 值的引用(即函数参数)存储在 CPython 的堆栈(帧)中?

Posted

技术标签:

【中文标题】为啥对 Python 值的引用(即函数参数)存储在 CPython 的堆栈(帧)中?【英文标题】:Why are references to python values, that are function parameters, stored on the stack(frame) in CPython?为什么对 Python 值的引用(即函数参数)存储在 CPython 的堆栈(帧)中? 【发布时间】:2022-01-20 04:52:42 【问题描述】:

Python 使用引用计数。这意味着,如果不再引用某个值,则该值的内存将被回收。或者换句话说。只要至少还有一个剩余引用,就不会删除obj,也不会释放内存。

让我们考虑以下示例:

def myfn():                    
    result = work_with(BigObj()) # reference 1 to BigObj is on the stack frame.
                                 # Not yet counting any 
                                 # reference inside of work_with function
                                 # after work_with returns: The stack frame 
                                 # and reference 1 are deleted. memory of BigObj  
                                 # is released
    return result             

def work_with(big_obj):       # here we have another reference to BigObj
    big_obj = None            # let's assume, that need more memory and we don't  
                              # need big_obj any_more
                              # the reference inside work_with is deleted. However,
                              # there is still the reference on the stack. So the                                                                           
                              # memory is not released until work_with returns
    other_big_obj = BigObj()  # we need the memory for another BigObj -> we may run  
                              # out of memory here

所以我的问题是:

为什么 CPython 对传递给堆栈上的函数的值持有额外的引用?这背后有什么特殊目的,还是只是一个“不幸”的实现细节?

我对此的第一个想法是: 防止引用计数降至零。但是,我们在被调用函数中仍然有一个活动引用。所以这对我来说没有任何意义。

【问题讨论】:

我认为这背后没有任何理由。这正是 CPython 在函数调用中实现临时引用的方式。出于同样的原因,sys.getrefcount() 给原始引用 +1,因为堆栈帧中的临时引用。 这可能是相关的:***.com/questions/46144076/… 非常有趣。行为从 3.5(无附加参考)更改为 3.6(有附加参考)。 【参考方案1】:

这是 CPython 将参数传递给函数的方式。 frame 持有对其参数的引用以允许传递临时对象。并且框架只有在函数返回时才会被销毁,所以所有参数在函数调用过程中都会得到一个额外的引用。

这就是为什么sys.getrefcount 的文档说:

返回的计数通常比您预期的高一,因为它包含(临时)引用作为 getrefcount() 的参数。

事实上,在被调用者中,对参数的引用被称为借用引用,这意味着被调用者永远不必递减它。因此,当您将其设置为None 时,它不会破坏该对象。

一个不同的实现是可能的,被调用者应该减少对其参数的引用。这样做的好处是可以立即销毁临时人员。但缺点是被调用者应该显式地减少其所有参数的引用计数。在 C 级别,引用计数已经很乏味了,我假设 Python 实现者为了简单而做出了这种选择。

顺便说一句,只有在将大型临时对象传递给不是最常见用例的函数时才有意义。

TL/DR:恕我直言,阻止函数立即销毁临时函数并没有真正的理由,这只是 CPython 中函数的一般实现的结果。

【讨论】:

以上是关于为啥对 Python 值的引用(即函数参数)存储在 CPython 的堆栈(帧)中?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能在同一个结构中存储一个值和对该值的引用?

在c语言中子函数引用主函数中的值和引用主函数某个值的地址的区别

12 函数返回值的地址引用

Java 值传递 和引用传递

C#在方法调用中,参数按值传递与按引用传递的区别是啥?

值的引用传递数组作为函数参数