为什么这个赋值对象与原对象共享同一内存空间?
在使用Python的itertools模块中的groupby时,我遇到了一个奇怪的现象。
在Python中,给变量赋值意味着给这个新变量分配一块自己的内存,而不是指向原来的内存(如果我理解错了,请告诉我):
y = 7
x = y
y = 9
x will still be 7
但是当我使用groupby模块时,我是用这个模块把有相同键的项目分到一个组里。我想要两个组,因为重新遍历原来的组是没用的,因为内存已经被修改了。举个例子:
for key, group in groupby(rows, lambda x: x[0]):
data = [thing[1] for thing in group] #accesses 1st attribute of element
data2 = [thing[2] for thing in group] # would yield [] as group is empty
所以我试了这个:
for key, group in groupby(rows, lambda x: x[0]):
#create a copy of group to reiterate over
toup = group
print toup #<itertools._grouper object at 0x1039a8850>
print group #<itertools._grouper object at 0x1039a8850>
data = [thing[1] for thing in group] #accesses 1st attribute of element
data2 = [thing[2] for thing in toup]
data2应该能访问第二个项目,但却返回了[],因为它们共享了同一块内存。
我想问的是,为什么会这样?把group赋值给toup,难道不应该让toup在不同的内存地址上有一份group的副本吗?
还有,我该怎么做才能绕过这个问题,这样我就不需要写两次groupby的循环了?
2 个回答
在Python中,给变量赋值意味着给新变量分配自己的内存,而不是指向原来的内存。
Python有可变对象(比如列表、迭代器,几乎所有东西)和不可变对象(比如整数和字符串)。无论是哪种情况,赋值都不会复制对象。对于不可变对象,所有对它们的操作都会生成一个新的实例,所以你不会像处理可变类型那样遇到“修改”整数或字符串的问题。
我的问题是,为什么会这样?把group赋值给toup,不应该意味着toup会在不同的内存地址上有一个group的副本吗?
这两个变量都会指向同一个对象。当你遍历一个变量并用完迭代器后,再遍历第二个变量时,会得到一个空的序列。
你说:
在Python中,变量赋值意味着给新变量分配自己的内存,而不是指向原始内存(根据我的理解,如果我说错了请告诉我):
这个说法不对。Python中的名字有些时候像C语言的变量,有些时候又像C语言的指针。试图把它们归类为其中一种只会让人困惑。不要这样想。应该把它们看作是Python独特的特性。
Python中的“变量”更应该被理解为名字。多个名字可能指向同一个内存位置,即使你并不想这样。
举个例子:
>>> y=7
>>> x=7
>>> x is y
True
>>> id(x)
140316099265400
>>> id(y)
140316099265400
而且(由于字符串驻留,以下内容可能是正确的。有关短整型驻留的更多信息,请参见PEP 237,但这属于实现细节):
>>> x=9
>>> y=5+4
>>> x is y
True
Python中的is
运算符通过比较内存地址来判断两个对象是否相同,如果相同则返回True。id
函数返回这个地址。
再举个例子:
>>> li1=[1,2,3]
>>> li2=[1,2,3]
>>> li1==li2
True
>>> li1 is li2
False
即使li1 == li2,它们也必须是不同的列表,否则如果你改变其中一个,两个都会改变,如下例所示:
>>> li1=[1,2,3]
>>> li2=li1
>>> li1.append(4)
>>> li2
[1, 2, 3, 4]
>>> li1==li2
True
>>> li1 is li2
True
(一定要理解另一个经典错误,所有Python程序员迟早都会犯。这是因为多个名字引用了同一个可变对象,然后期望其中一个名字像单一对象那样工作。)
正如jonrsharpe在评论中指出的,建议阅读Ned Batchelder的优秀文章关于Python名字和值的事实与误区或如何像Pythonista那样思考,以获取更详细的概述。