使用对元素的现有引用修改列表?

时间:2015-07-18 05:46:59

标签: python list

(如果你想跳过2 A.M. Python科学并切入追逐,我的问题总结在最后)

请考虑以下事项:

1: animals = ['cat', 'cow', 'donkey', 'horse']  # we start with a list
2: animals_reference = animals  # make another reference and assign it to animals

3: cat = animals[0]  # refer cat to first element of animals
4: assert cat is animals[0]  # no copy occurred, still same object

5: animals[0] = animals[0].capitalize()  # change first element of list
6: assert cat is not animals[0]  # animals[0] now refers to another object
7: assert animals_reference is animals  # animals still points to the same object as before

我的理解是Python列表的底层结构是一个C数组(有很多动态的东西在继续,但最后仍然是一个C数组。)

令我困惑的是:我们设置cat来引用列表的第一个元素(3)。在C中,它将它引用到数组的第一个元素的地址。

然后我们修改列表的第一个元素(5)。

但在这之后,猫不再引用那个物体(6)。但是,列表引用也没有改变,因为在(7)中我们看到它从一开始就指向了同一个对象。

这让我大吃一惊,因为它表明猫现在指的是其他东西,即使它从未被重新分配。

所以我进行了以下实验:

cat = animals[0]  # refer cat to first element of animals
assert cat is animals[0]  # no copy occurred, still same object
print("id of cat:        {}".format(hex(id(cat))))
print("id of animals[0]: {}".format(hex(id(animals[0]))))
print("id of animals[]:  {}".format(hex(id(animals))))

print("capitalizing animals[0]...")
animals[0] = animals[0].capitalize()
print("-id of cat:        {}".format(hex(id(cat))))
print("-id of animals[0]: {}".format(hex(id(animals[0]))))
print("-id of animals[]:  {}".format(hex(id(animals))))

输出:

id of cat:         0xffdda580
id of animals[0]:  0xffdda580
id of animals[]:   0xffddc828
capitalizing animals[0]...
-id of cat:        0xffdda580  # stayed the same!
-id of animals[0]: 0xffe12d40  # changed!!
-id of animals[]:  0xffddc828

这让我相信Python列表不一定是内存的连续元素,对元素的更改只会指向内存中的其他位置?我的意思是,数组的第一个元素的地址在内存中早于数组本身的地址!

列出使用的底层结构究竟是什么解释了我所看到的内容?

4 个答案:

答案 0 :(得分:1)

在python中,一切都是对象,这意味着一切都存储在堆中。

定义时

animals = ['cat', 'cow', 'donkey', 'horse']

每个字符串('cat',...)都存储在堆中。列表animals包含对每个字符串的引用。

分配cat = animals[0],让cat持有对字符串' cat'的引用(animals[0]持有相同的参考。

分配animals[0] = animals[0].capitalize()会创建一个新字符串('Cat'),并将animals[0]持有的引用更改为新字符串。但是,cat仍然保留对堆中原始对象的引用。

答案 1 :(得分:1)

这是考虑它的一种方式:

1: animals = ['cat', 'cow', 'donkey', 'horse']  # we start with a list
2: animals_reference = animals  # make another reference and assign it to animals

3: cat = animals[0]  # refer cat to first element of animals

这并不能使cat指的是“动物的第一要素”,至少不是你所说的那种方式。它使cat指的是动物的第一个元素所指的任何东西。在这种情况下,这是字符串“cat”。换句话说,表达式animals[0]本身就是对象的引用。该对象是字符串cat。当您执行animals[0]时,您将获得表达式animals[0]所引用的对象。执行cat = animals[0]时,请设置cat以引用该对象。

无法避免“取消引用”值animals[0]。也就是说,没有办法说“给我animals[0]的指向,以便当animals[0]开始指向别的东西时,我的新变量也会指向其他东西”。您只能得到animals[0]所指的内容,而不是其引用本身。

因此:

4: assert cat is animals[0]  # no copy occurred, still same object

5: animals[0] = animals[0].capitalize()  # change first element of list

在此您可以更改animals[0]点的内容。但是,您将cat设置为animals[0]用于指向的内容。所以现在catanimals[0]指向不同的东西。字符串"cat"没有改变(这就是为什么你的is测试仍然显示值相同的原因);只是animals[0]停止指向该字符串并开始指向字符串"Cat"

答案 2 :(得分:0)

cat的ID不会发生变化,因为它是对原始 animals列表的第一个元素的引用。当该元素发生更改(通过大写)时,animals[0]的ID会更改,但cat不会更改,因为它是原始 {{1 },这是对包含字母 animals[0] 的字符串对象的引用,而不是当前cat的引用,现在它是< em>另一个包含字母 animals[0] 的字符串对象。

列表Cat仍然存在,并且已就地修改,因此其ID不会更改。由于列表是可变的,因此在最初创建时,不能只是一个连续的内存区域,因为可能会添加一个大于预先分配的内存块的对象。

Python列表是一个动态对象,包含对其他对象的引用(如果它是空的,则没有任何内容)。如果它只是一个静态的C数组,那么使用Python是没有意义的,你只需要使用C. Python列表的一点(好吧,其中一个,无论如何)是他们&#39; >可变 - 动态,可变,可重新排序,可伸缩,可折叠,可排序等。

答案 3 :(得分:0)

以C为例 -

假设A是一个数组,并且数组包含对象(不是基元),

可以说,A按顺序存储在地址 - 1000, 1008, 1016 , etc中,这是第一个元素的引用存储在地址 - 1000中。让我们说第一个元素本身存储在2000

当你这样做时 -

cat = A[0]

您没有获得存储A的第一个元素的地址(而是)获得对第一个元素的引用的地址。那就是你得到1000,得到2000

现在,如果您更改存储在A [0]中的元素以说明地址3000中的对象,那么您将地址1000中的引用更改为3000。你认为猫的参考会改变吗?