这事算是告一段落了

2020.9.6号更新

昨晚和死党聊天的时候,提到一个问题,说python变量的作用域是个有意思的东西,然后就随口说了几句,因为我之前比较熟 c 和 c++ 所以都习惯先定义在使用(Python里面就是先赋值再使用,使用之前我一定要保证他指向的内存里面有我想要的东西),所以一般我都没遇到他说的问题.。然后我就突然来了一句,“你说改变列表值是在原地址上修改呢,还是新开辟内存放一个值,然后把这个内存起个别名”

例如

a = [1,2,3,4]

a[1] = 20

我想把 2 改成 20 ,是在原来 a[1] 的地址上改呢?还是我从新开辟了一个内存来放 20 这个数,然后给这个内存起了一个别名叫 a[1]

这篇文章的起源就是来自这儿,列表到底是顺序存储还是链式存储,也是个问题

首先看看一个简单的例子

a = [1,2,3,4]
print("原始的各元素id:")
for i in range(len(a)):
print(id(a[i]))
a.append(5)
print("添加一个元素后各元素id:")
for i in range(len(a)):
print(id(a[i]))
a.append(6)
print("再添加一个元素后各元素id:")
for i in range(len(a)):
print(id(a[i]))

输出

原始的各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

添加一个元素后各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

8791490614432

再添加一个元素后各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

8791490614432

8791490614464

可以看到

a[0]

的 id 是 8791490614304 ,

a[1]

是8791490614336,一直到

a[4]

是 8791490614432,每两个之间 32 位(4字节,符合我们 int 型 4 字节的印象),看到这个我们猜测 list 是顺序结构的,

问题来了

看下面的例子

a = [1,2,3,4]
print(a)
for i in range(len(a)):
print(id(a[i]))
a[1] = 20
print(a)
print(id(a[1]))

输出

[1, 2, 3, 4]

8791490614304

8791490614336

8791490614368

8791490614400

[1, 20, 3, 4]

8791490614912

我们

a[1] = 20

想把 2 改成 20 ,改完后发现

a[1]

的 id 变了,原来是 8791490614336,现在变成了8791490614912,相差 576bit(位),也就是 72byte(字节),而且输出的列表也的确是变成了20 ,这就怪了

我来点骚操作,我在后面 append 添加16个数(16个数加

a[1]

后面的

a[2]

a[3]

总共18个数刚好72字节)又会是啥情况呢,先试试添加一个是不是从 8791490614400 到 8791490614432

a = [1,2,3,4]
print("原始的各元素id:")
for i in range(len(a)):
print(id(a[i]))
a[1] = 20
print("修改后的a[1]id:",id(a[1]))
a.append(5)
print("添加一个元素最后元素id:",id(a[-1]))

输出

原始的各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

修改后的a[1]id: 8791490614912

添加一个元素最后元素id: 8791490614432

看到,的确是 8791490614432 ,好的我们开始骚操作,添加16个后看看情况

a = [1,2,3,4]
print("原始的各元素id:")
for i in range(len(a)):
print(id(a[i]))
a[1] = 20
print("修改后的a[1]id:",id(a[1]))
for i in range(16):
a.append(i+5)
print("添加18个元素后各元素id:")
for i in range(len(a)):
print(id(a[i]))

输出

原始的各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

修改后的a[1]id: 8791490614912

添加18个元素后各元素id:

8791490614304

8791490614912

8791490614368

8791490614400

8791490614432

8791490614464

8791490614496

8791490614528

8791490614560

8791490614592

8791490614624

8791490614656

8791490614688

8791490614720

8791490614752

8791490614784

8791490614816

8791490614848

8791490614880

8791490614912

8791490614912 出现了两次,而且和我们预料的最后一个应该是 8791490614912 也完全一致

此时输出 a 的所有元素是这样的

[1, 20, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

那么为什么;8791490614912 出现了两次呢,原因我想我大概清楚了,因为 地址(id)是 8791490614912 的这块内存有两个别名

a[1]

a[19]

,内容都是20,

所以为什么修改成 20 地址会变到 8791490614912呢,我觉得应该是在原来的地址上往后移动了

20(目标值)-2(初始值)

个整数所占的字节,这样一来似乎 list 就是顺序存储的

但是结论还为实过早,多做下实验

为了观察下先删除最后一个再添加一个是啥情况,做了下面实验,分别测试下pop()、 remove()、del()

pop()
a = [1,2,3,4]
print("原始的各元素id:")
for i in range(len(a)):
print(id(a[i]))
poplist = a.pop() #把 4 删掉
a.append(4) # 在[1,2,3]后面加一个4
print("删除后再添加的各元素id:")
for i in range(len(a)):
print(id(a[i]))
print("pop方法返回值的id:",id(poplist))

输出

8791490614304

8791490614336

8791490614368

8791490614400

删除后再添加的各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

pop方法返回值的id: 8791490614400

可以看到原来列表的最后一个元素的 id 8791490614400里面还是有数的,而且这个数是4(原来列表的最后一个元素值),

poplist = a.pop()

只是把这个地址叫另一个别名 poplist ,然后我 append 一个 4 到列表后面,id 还是原来的 8791490614400。说明添加是在原来的地址

a[-1](即a[3])

往后移动了

4(添加的值)- 3(原列表最后一个元素的值)

个整数所占的字节,再次说明是顺序存储的

为了证明我说的

添加是在原来的最后地址

a[-1]

往后移动了

添加的值 - 原列表最后一个元素的值

个整数所占的字节

, 我再 append 一次看看情况

a = [1,2,3,4]
print("原始的各元素id:")
for i in range(len(a)):
print(id(a[i]))
poplist = a.pop()
a.append(4)
print("删除后再添加的各元素id:")
for i in range(len(a)):
print(id(a[i]))
print("pop方法返回值的id:",id(poplist))
a.append(10)
print("再添加一次的各元素id:")
for i in range(len(a)):
print(id(a[i]))

输出

原始的各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

删除后再添加的各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

pop方法返回值的id: 8791490614400

再添加一次的各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

8791490614592

可以看到从 8791490617400 跳到了8791490614592,192 / 32 = 6,6 = 10 - 4,10是添加的值,4是原列表最后一个值,是不是证明了我的观点,你可以试试 添加 104, id 肯定相差 32 *(104 - 4)= 3200位

再看看 remove() 是不是也一样呢

remove()
a = [1,2,3,4]
print("原始的各元素id:")
for i in range(len(a)):
print(id(a[i]))
a.remove(4) #把 4 删掉
a.append(4) # 在[1,2,3]后面加一个4
print("删除后再添加的各元素id:")
for i in range(len(a)):
print(id(a[i]))

输出

原始的各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

删除后再添加的各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

可以看到,前后 id 是一样的,和 pop 也一样

再来一个移除中间的,比如2

a = [1,2,3,4]
print("原始的各元素id:")
for i in range(len(a)):
print(id(a[i]))
a.remove(2)
a.append(2)
print("删除后再添加的各元素id:")
for i in range(len(a)):
print(id(a[i]))

输出

原始的各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

删除后再添加的各元素id:

8791490614304

8791490614368

8791490614400

8791490614336

最后一个 id 是原来 a[1] 的 id ,说明我总结的

添加是在原来的最后地址

a[-1]

往后移动了

添加的值 - 原列表最后一个元素的值

个整数所占的字节

,是对的,差是正数就向后移,差是负数,向后移负数个,不就是向前移吗

最后看看del会有啥不同不

del()

删除最后的

a = [1,2,3,4]
print("原始的各元素id:")
for i in range(len(a)):
print(id(a[i]))
del(a[3])
a.append(4)
print("删除后再添加的各元素id:")
for i in range(len(a)):
print(id(a[i]))

输出

原始的各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

删除后再添加的各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

删除中间的

a = [1,2,3,4]
print("原始的各元素id:")
for i in range(len(a)):
print(id(a[i]))
del(a[1])
a.append(2)
print("删除后再添加的各元素id:")
for i in range(len(a)):
print(id(a[i]))

输出

原始的各元素id:

8791490614304

8791490614336

8791490614368

8791490614400

删除后再添加的各元素id:

8791490614304

8791490614368

8791490614400

8791490614336

也一样,三种删除都试过了,都是一样的

总结

list (列表)是顺序存储的

修改列表元素的值:在原来的地址上往后移动了

目标值 - 原始值

个整数(列表数据类型)所占的字节,用来存放目标值,然后给这块地址起别名,别名就是原始值的索引。比如

a[1] = 20

,别名就叫

a[1]

添加:在原来的最后地址

a[-1]

往后移动了

添加的值 - 原列表最后一个元素的值

个整数(列表数据类型)所占的字节,用来存放要添加的值,然后起个别名。这儿的别名是原列表的最后索引+1,比如原来列表有四个元素,最后索引是

a[3]

,进行一次添加操作后,新添加的这个元素的别名就是

a[4]

差是正数就向后移,差是负数,向后移负数个,不就是向前移吗