(如果你想跳过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列表不一定是内存的连续元素,对元素的更改只会指向内存中的其他位置?我的意思是,数组的第一个元素的地址在内存中早于数组本身的地址!
列出使用的底层结构究竟是什么解释了我所看到的内容?
答案 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]
用于指向的内容。所以现在cat
和animals[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
。你认为猫的参考会改变吗?