1

我对以下情况感到困惑,也许我的词汇在这里是错误的,所以很抱歉。

假设我们有一个元组,x = ('a', [])然后我们做x = (x[0], [1, 2, 3]).

由于新元组引用了旧元组,我们不能删除旧元组对象,但由于旧元组对象我们只使用对 x[0] 的引用,所以我们正在浪费内存作为旧元组的 x[1]元组对象不能被任何东西访问。

这是内存泄漏的真实案例吗?我们是否只是为了让新元组对象的引用有意义而浪费内存。

4

3 回答 3

5

您可能是从像 C++ 这样的语言开始使用 Python 的,其中变量是存储值的内存位置。在 Python 中,值存储在您不必担心的地方,而您的变量只是引用这些值的名称价值观。没有办法让一个变量引用另一个变量*——您可以让它引用与另一个变量相同的,但这不是问题。


例如,在 C++ 中:

int x[] = {1, 2, 3};
int &y = x[0];

这里,x是一个足以容纳三个int值的内存位置,并且y是对这些位置中的第一个的引用。所以,如果在还活着的时候x就消失了y,你会有一个悬空的参考,这会很糟糕。但在 Python 中:

x = [1, 2, 3]
y = x[0]

这里,x是一个list对象的名称,它的三个位置是int存储在别处的三个不同对象的名称,并且y只是第一个int对象的另一个名称。如果x消失,Python 可以释放list, 之后它可以释放2and3对象**(因为没有其他人引用它们),只留下1对象。


这也是 Python 中没有“复制构造函数”*** 的原因。在 C++ 中,int z = x[0]创建一个新的内存位置,并将intfrom隐式复制x[0]到该内存位置;在 Python 中,除非你明确地编写类似的东西,否则你z = copy.copy(x[0])永远不会复制任何东西。


* 如果您查看封闭单元如何在幕后工作,这并不完全正确。

** 事实上,小整数通常会被特殊对待,并且永远保持活动状态,但让我们忽略这一点。

*** 少得多的复制赋值运算符、移动构造函数和移动赋值运算符。

于 2013-11-05T20:05:23.620 回答
4

这是一个带注释的 Python 会话,使用您的示例来显示当前引用和未引用的内容。

>>> x = ('a', [])
>>> id(x)
4340112344            # The tuple
>>> id(x[0])
4339406128            # The character 'a'
>>> id(x[1])
4340109184            # An empty list
>>> x = (x[0], [1,2,3])
>>> id(x)
4340112488            # x refers to a new object
>>> id(x[0])
4339406128            # The character 'a'. The old tuple is not involved
>>> id(x[1])
4340199576            # A new list.

原始元组对象 4340112344 不再被引用,因此可以在方便时自由地进行垃圾回收,而不会影响x.

于 2013-11-05T20:03:57.537 回答
-2

轻微的逻辑错误。

Python 字符串是不可变的。当你这样做时:

x = ('a', [])

然后这个:

x = (x[0], [1,2,3])

x[0]现在是一个新字符串,即不绑定到原来的字符串。因此 GC 可以自由地收集原始元组。

以上无效!!!哎呀!!!!!!!逻辑错误!!!下面的文字对所有类型都有效!!!

但是,如果我们这样做:

x = (x[1], 'Hi!')

原始元组仍然被删除,即使x[1]是对列表的引用。为什么?因为列表没有绑定到元组。两者是独立的对象。因此,当您引用 时x[1],可以安全地删除其他元素以及原始元组本身。

底线:如果一个对象是活着的,那并不意味着它的父对象是活着的。如果我们继续覆盖x,它每次都会被删除,除了一个需要保持活动的对象。

所以,没有内存泄漏。

于 2013-11-05T20:02:46.207 回答