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

Python 使用对元素的现有引用修改列表?,python,list,Python,List,(如果你想跳过凌晨2点的Python science,直接切入主题,我的问题在最后总结) 考虑以下几点: 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 e

(如果你想跳过凌晨2点的Python science,直接切入主题,我的问题在最后总结)

考虑以下几点:

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)

但这样做之后,cat不再引用该对象(6)。然而,列表引用也没有改变,因为在(7)中,我们看到它从一开始就指向同一个对象

这让我心神不宁,因为它表明cat现在指的是其他东西,尽管它从未被重新分配过任务

所以我做了以下实验:

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列表不一定是内存中连续的元素,对元素的更改只会指向内存中的其他地方?我的意思是,数组的第一个元素的地址在内存中比数组本身的地址早


列表使用的底层结构究竟是什么来解释我所看到的?

猫的id不会改变,因为它是对原始
动物
列表的第一个元素的引用。当该元素更改(大写)时,
动物[0]
的id会更改,但
不会更改,因为它是对原始
动物[0]
的引用,是对包含字母
的字符串对象的引用,而不是当前的
动物[0]
,它现在是对另一个字符串对象的引用,其中包含字母
Cat

列表<代码>动物
仍然存在,并且已经修改到位,因此其id不会更改。由于列表是可变的,它们在最初创建时不能只是一个连续的内存区域,因为可以添加一个比预先分配的内存块大的对象


Python列表是一个动态对象,包含对其他对象的引用(如果为空,则不包含任何内容)。如果它只是一个静态的C数组,那么使用Python就没有意义了,你只需要使用C。Python列表的意义在于它们是可变的——动态的、可变的、可重排序的、可伸缩的、可折叠的、可排序的等等。

在Python中,所有东西都是一个对象,这意味着所有东西都存储在堆中

当你定义

animals = ['cat', 'cow', 'donkey', 'horse']
每个字符串(
'cat'
,…)都存储在堆中。列表
animals
包含对每个字符串的引用

分配
cat=animals[0]
,使
cat
保留对字符串“cat”的引用(与
animals[0]
保留的引用相同)


animates[0]=animates[0]。大写()
创建一个新字符串(
'Cat'
),并将
animates[0]
持有的引用更改为新字符串。但是,
Cat
仍持有对堆中原始对象的引用。

以C为例-

假设
A
是一个数组,该数组包含对象(不是基本体)

比方说,A按顺序存储在地址-
1000、1008、1016等中,即第一个元素的引用存储在地址-
1000中。比方说第一个元素本身存储在
2000中

当你这样做的时候-

cat = A[0]
您得不到存储A的第一个元素的地址,而是获得了对第一个元素的引用。也就是说,您得不到
1000
,而是
2000


现在,如果您将[0]中存储的元素更改为地址3000中的对象,也就是说,您将地址
1000
中的引用更改为
3000
。您认为cat的引用会更改吗?

这里有一种方法可以考虑:

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”。换句话说,表达式
anists[0]
本身就是一个对象的引用。该对象是字符串
cat
。当您执行
animals[0]
操作时,您将得到表达式
animals[0]
所指的对象。当您执行
cat=animals[0]
操作时,您将
cat
设置为引用该对象

没有办法避免“取消引用”值
anists[0]
。也就是说,没有办法说“给我
anists[0]
的指向性,这样当
anists[0]
开始指向其他对象时,我的新变量也将指向其他对象”。你只能得到
anists[0]的内容
指的是,而不是指它本身

因此:


这里您更改了
动物[0]
指向的对象。但是您将
设置为
动物[0]
过去指向的对象。因此现在
动物[0]
指向不同的对象。字符串
没有更改(这就是为什么
is
测试仍然显示值相同的原因);这只是因为
动物[0]
不再指向那个字符串,而是开始指向字符串
“Cat”

所以列表更像是指向对象的通用指针数组,而不是对象本身的容器(可能除了原语或其他东西)@andy没有Python中的“原语”——一切都是一个对象。我提到的引用有点像指针
4: assert cat is animals[0]  # no copy occurred, still same object

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