首页 > 解决方案 > 为什么python内存分配会这样?

问题描述

考虑以下代码,该代码用于查找所有带有 n 个括号的有效括号位置。

def paren(n):
    ans = []
    def helper(string, left, right,n,foo):
        if len(string)==2*n:
            ans.append(string)
        if left > 0:
            helper(string+'(', left-1,right,n)
        if right > left:
            helper(string+')', left,right-1,n)
    helper('',n,n,n)

如果我们添加一个列表(在函数中没有实际用途),我们得到

def paren(n):
    ans = []
    def helper(string, left, right,n,foo):
        print(hex(id(foo)), foo)
        if len(string)==2*n:
            ans.append(string)
        if left > 0:
            helper(string+'(', left-1,right,n,[])
        if right > left:
            helper(string+')', left,right-1,n,[])
    helper('',n,n,n,[])
    
paren(2)

OUTPUT:

0x2e5e2446288 []
0x2e5e28e3508 []
0x2e5e28e3688 []
0x2e5e26036c8 []
0x2e5e27bafc8 []
0x2e5e28e3688 []
0x2e5e26036c8 []
0x2e5e27bafc8 []

而如果我们每次都明确地传递 foo 那么我们得到

def paren(n):
    ans = []
    def helper(string, left, right,n,foo):
        print(hex(id(foo)), foo)
        if len(string)==2*n:
            ans.append(string)
        if left > 0:
            helper(string+'(', left-1,right,n,foo)
        if right > left:
            helper(string+')', left,right-1,n,foo)
    helper('',n,n,n,[])
    
paren(2)

OUTPUT:

0x1c2cfec6288 []
0x1c2cfec6288 []
0x1c2cfec6288 []
0x1c2cfec6288 []
0x1c2cfec6288 []
0x1c2cfec6288 []
0x1c2cfec6288 []
0x1c2cfec6288 []

在第一种情况下,我们在内存中获得了一个不同的对象,为什么与第二种情况相比,我认为这与我们创建一个新列表而不是传递函数参数有关?

然而,当我们向 foo 添加一些东西时,我们会得到与第一种情况相同的行为:

def paren(n):
    ans = []
    def helper(string, left, right,n,foo):
        print(hex(id(foo)), foo)
        if len(string)==2*n:
            ans.append(string)
        if left > 0:
            helper(string+'(', left-1,right,n,foo+['bar'])
        if right > left:
            helper(string+')', left,right-1,n,foo+['bar'])
    helper('',n,n,n,[])
    
paren(2)

OUTPUT:

0x269572e6288 []
0x26959283548 ['bar']
0x26957363688 ['bar', 'bar']
0x2695925ae88 ['bar', 'bar', 'bar']
0x26957363408 ['bar', 'bar', 'bar', 'bar']
0x2695925ae88 ['bar', 'bar']
0x26957363408 ['bar', 'bar', 'bar']
0x269592833c8 ['bar', 'bar', 'bar', 'bar']

但奇怪的是,如果我们为 foo 传递一些 int,我将取 5 进行演示,我们得到:

def paren(n):
    ans = []
    def helper(string, left, right,n,foo):
        print(hex(id(foo)), foo)
        if len(string)==2*n:
            ans.append(string)
        if left > 0:
            helper(string+'(', left-1,right,n,5)
        if right > left:
            helper(string+')', left,right-1,n,5)
    helper('',n,n,n,5)
    
paren(2)

OUTPUT:

0x7ffef47293c0 5
0x7ffef47293c0 5
0x7ffef47293c0 5
0x7ffef47293c0 5
0x7ffef47293c0 5
0x7ffef47293c0 5
0x7ffef47293c0 5
0x7ffef47293c0 5

即内存中的同一点。

但是,如果我将上述代码中的 5 替换为更大的 int,例如 2550,我会得到以下结果:

0x2519f6d4790 2550
0x2519f9ec6f0 2550
0x2519f9ec6f0 2550
0x2519f9ec6f0 2550
0x2519f9ec6f0 2550
0x2519f9ec6f0 2550
0x2519f9ec6f0 2550
0x2519f9ec6f0 2550

所以最初它存储在不同的内存地址,但每个后续调用都在相同的地址。为什么这与 foo=5 的情况不同,这里发生了什么?

同样在内存地址在调用之间发生变化的示例中,我确实看到不止一次使用相同的内存地址,例如:

...
0x2695925ae88 ['bar', 'bar', 'bar']
...
0x2695925ae88 ['bar', 'bar']
...

为什么会这样?一旦旧变量不再在递归调用堆栈中,python 是否使用以前使用的内存地址来存储新变量?

我对这些行为真的很模糊,所以如果有人能帮助我,那就太好了!

我听说过诸如按引用传递和按值传递之类的东西,但我不太确定它们的含义以及它是否与这个 python 示例有关。

谢谢你。

标签: pythonpython-3.xmemoryparameter-passingpass-by-reference

解决方案


第一个代码:调用堆栈保留对 each 的引用foo,因此您在内存中一次有许多列表,每个列表都有一个唯一的 ID。

第二个代码:您将相同的列表(最初为空)传递给每个递归调用。

第三个代码:Cpython,作为特定于实现的优化,缓存小int常量以供重用

第四个代码 Cpython 不会缓存大(即大于 256),所以每次出现 2550 都会创建一个新int对象。


推荐阅读