我对以下情况感到困惑,也许我的词汇在这里是错误的,所以道歉。
假设我们有一个元组x = ('a', [])
,然后我们会x = (x[0], [1, 2, 3])
。
由于新元组引用了旧元组,我们无法删除旧的元组对象,但由于旧的元组对象,我们只使用对x [0]的引用,因此我们将内存浪费为x [1]任何东西都无法访问旧元组对象。
这是内存泄漏的真实案例吗?我们是在浪费内存,以便新的元组对象的引用有意义。
答案 0 :(得分: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
,之后它可以释放2
和3
个对象**(因为没有其他人引用它们),只留下1
对象。
这也是Python中没有“复制构造函数”***的原因。在C ++中,int z = x[0]
创建一个新的内存位置,并隐式地将int
从x[0]
复制到该内存位置;在Python中,除非你明确写出类似z = copy.copy(x[0])
之类的内容,否则你永远不会复制任何内容。
*如果你看看封闭细胞是如何在封面下工作的,那就不是真的了。
**事实上,小整数通常是专门处理的,并且永远保持活着,但让我们忽略它。
***更少的拷贝赋值运算符,移动构造函数和移动赋值运算符。
答案 1 :(得分: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
引用的新对象。
答案 2 :(得分:-2)
轻微的逻辑错误。
Python字符串是不可变的。执行此操作时:
x = ('a', [])
然后这个:
x = (x[0], [1,2,3])
x[0]
现在是一个新字符串,即与原始字符串无关。因此,GC可以自由收集原始元组。
以上是无效的!!! Acck !!!!!!! MISSHAPED LOGIC !!!较低的文字对所有类型都有效!!!
然而,如果我们这样做:
x = (x[1], 'Hi!')
即使x[1]
是对列表的引用,原始元组仍会被删除。为什么?因为列表与元组无关。这两个是独立的对象。因此,当您引用x[1]
时,可以安全地删除其他元素以及原始元组本身。
底线:如果一个物体还活着,那并不意味着它的父物是活着的。如果我们继续覆盖x
,则每次都会删除它,除了需要保持活动的一个对象。
所以,没有内存泄漏。