列表(list)
列表(list)作为 Python 中最常用的数据类型之一,是一个可增加、删除元素的可变(mutable)容器。
基本操作
创建list
只要使用一对中括号[]。如下示例:
1 | empty = [] |
内存示意图
empty 在内存中的示意图:

lst 在内存中的示意图:

lst2 在内存中的示意图:

获取list长度(个数)
使用 Python 的内置函数 len 获取 list 内元素个数
1 | empty = [] |
执行结果:

遍历list并获取元素类型
遍历lst内每个元素并获取对应的类型:
for in对list进行遍历type内置函数获取元素类型
1 | lst = [1, 'xiaoming', 29.5, '17312662388'] |
执行结果:

可以看到输出的数据类型都不同,这说明,Python的列表是不要求元素类型一致。
获取list中元素
使用索引访问list中每个位置的元素,索引从0开始,通过索引获取lst2中的元素:
1 | lst2 = ['001', '2019-11-11', ['三文鱼', '电烤箱']] |
执行结果:

当索引超出了范围时,Python会报一个IndexError错误,所以,要确保索引不要越界,记得最后一个元素的索引是len(classmates) - 1。
也可以从列表的最后一个值取值,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素:
1 | empty = [] |
执行结果:

当然,倒数第4个就越界了。
常用列表内置函数
append
append() - 往list中追加元素到末尾
1
2
3
4lst = ['a', 'b', 'c']
lst.append('d')
lst
['a', 'b', 'c', 'd']
insert
Insert(索引值, 元素) - 把元素插入到指定的位置
1
2
3
4lst = ['a', 'b', 'c']
lst.insert(1, 'x')
lst
['a', 'x', 'b', 'c']
pop
pop() - 删除list末尾的元素
1
2
3
4
5lst = ['a', 'b', 'c']
lst.pop()
'c'
lst
['a', 'b']
pop(i) - 删除指定位置的元素,i索引值
1
2
3
4
5lst = ['a', 'b', 'c']
lst.pop(1)
'b'
lst
['a', 'c']
remove
移除列表中指定的元素
1
2
3
4lst = ['a', 'b', 'c']
lst.remove('b')
lst
['a', 'c']
已经学习了列表的基础操作后,下面如何要向 lst2 的第三个元素 ['三文鱼','电烤箱'] 内再增加一个元素 '烤鸭'。
使用列表的索引获取出该元素
1
2
3
4
5lst2 = ['001', '2019-11-11', ['三文鱼', '电烤箱']]
# sku是一个列表
sku = lst2[2]
print(sku)sku 变量位于栈帧中,同时指向 lst2[2]

使用列表的 append 方法增加元素,append 默认增加到 sku列表尾部:
1
2
3sku.append('烤鸭')
print(sku)
# 输出结果:['三文鱼', '电烤箱', '烤鸭']
此时又想在列表的指定位置,新加入
'牛腱子',使用列表的 insert 方法:1
2
3sku.insert(1, '牛腱子')
print(sku)
# 输出结果:['三文鱼', '牛腱子', '电烤箱', '烤鸭']
这时要把
'烤鸭'移除列表,使用列表的pop方法可直接移除列表尾部元素:1
2
3
4
5item = sku.pop()
print(item)
# 输出结果:烤鸭
print(sku)
# 输出结果:['三文鱼', '牛腱子', '电烤箱']
现在又想将
'三文鱼'移除,pop()能移除表尾元素,pop(i)可以移除指定的元素(通过索引值),这时我们也可以使用remove方法,移除传入的元素值1
2
3
4sku.remove('三文鱼')
# 更好使用: sku.remove(sku[0])
print(sku)
# 输出结果:['牛腱子', '电烤箱']
深浅拷贝
经过以上一系列操作后,这时我们现在输出列表lst
1 | print(lst2) |
发现第三个元素也对应改变,因为 sku 引用 lst2 的第三个元素,sku 指向的内存区域改变,所以 lst2 也会相应改变。
那么,如果不想改变 lst2 的第三个元素,就需要复制出 lst2 的这个元素,列表上有 copy 方法可实现复制:
1 | # lst2列表的初始值 |
可视化此行代码,lst2 位于全局帧栈中,其中三个元素内存中的可视化图如下所示:

浅拷贝(shallow copy)
这时,我们要将需要的数据拷贝出来
1 | lst2 = ['001', '2019-11-11', ['三文鱼', '电烤箱']] |
注意,copy 函数,仅仅实现对内嵌对象的一层拷贝,属于 shallow copy。
此时可视化图为如下,因为拷贝 lst2[2],所以 sku_deep 位于栈帧中指向一块新的内存空间:

此时,再对 sku_deep 操作,便不会影响 lst2[2] 的值。
如下修改 sku_deep 的第一个元素,我们在查看意向lst2列表中的数据:
1 | sku_deep[0] = '腱子' |
查看输出结果后,我们发现在修改,修改 sku_deep 时,不会影响 lst2[2]。
因为它们位于不同的内存空间中,如图所示,lst2[2] 中的第一个元素依然是“三文鱼”,而不是“腱子”。

深拷贝(deepcopy)
以上已经了解了浅拷贝的流程,那么深拷贝,又有什么不同呢?
我们看下,下面的例子,a 是内嵌一层 list 的列表,对其浅拷贝生成列表 ac,修改 ac 的第三个元素,也就是列表 [3, 4, 5] 中的第二个元素为 40:
1 | a = [1, 2, [3, 4, 5]] |
通过上面的流程,证明实现拷贝。
而 ac[2][1] 是否与原数组 a 的对应位置元素相等:
1 | print(a[2][1] == ac[2][1]) |
返回 True,进一步证明是浅拷贝,不是深拷贝。
如下图所示:copy 只完成了一层 copy,即 [1,2, id([3,4,5])] 复制一份,而复制后,仍然指向 [3,4,5] 所在的内存空间:

要想实现深度拷贝,需要使用 copy 模块的 deepcopy 函数:
1 | from copy import deepcopy |
打印结果,都为 False,结合下图,也能看出内嵌的 list 全部完成复制,都指向了不同的内存区域。

切片
Java 和 C++ 中,访问数组中的元素只能一次一个,但 Python 增加切片功能为访问列表带来极大便利。
首先,利用内置函数range(start, stop, step)生成序列数据,并转为list类型:
1 | a = list(range(1, 20, 3)) |
使用 a[:3] 获取列表 a 的前三个元素,形象称这类操作为“切片”,切片本身也是一个列表 [1,4,7]:
1 | a[:3] |
从以下几个例子,查看列表的切片:
使用 a[-1] 获取 a 的最后一个元素,返回 int 型,值为 19;
1
2a[-1]
19
使用 a[:-1] 获取除最后一个元素的切片 [1, 4, 7, 10, 13, 16];
1
2a[:-1]
[1, 4, 7, 10, 13, 16]
使用 a[1:5] 生成索引为 [1,5)(不包括索引 5)的切片 [4, 7, 10, 13];
1
2a[1:5]
[4, 7, 10, 13]
使用 a[1:5:2] 生成索引 [1,5) 但步长为 2 的切片 [4,10];
1
2a[1:5:2]
[4, 10]
使用 a[::3] 生成索引 [0,len(a)) 步长为 3 的切片 [1,10,19];
1
2a[::3]
[1, 10, 19]
使用 a[::-3] 生成逆向索引 [len(a),0) 步长为 3 的切片 [19,10,1]。
1
2a[::-3]
[19, 10, 1]
逆向:从列表最后一个元素访问到第一个元素的方向。
特别地,使用列表的逆向切片操作,只需一行代码就能逆向列表(列表元素反转):
1 | a = list(range(1, 20, 3)) |
元组(tuple)
元组既然是不可变(immutable)对象,自然也就没有增加、删除元素的方法。
基本操作
创建tuple
使用一对括号(())就能创建一个元组对象,如:
1 | a = () # 空元组对象 |
它们都是元组,除了 list 是用 [] 创建外,其他都与 list 很相似,比如都支持切片操作。
特别注意:一个整数加一对括号,比如 (10),返回的是整数。必须加一个逗号 (10, ) 才会返回元组对象。
可变与不可变
之前说到列表是一个可变容器,元组是一个不可变容器,那么可变与不可变有什么区别呢?可变与不可变是一对很微妙的概念,详情查看总结如下:
列表可变流程查看
创建一个列表 a = [1,3,[5,7],9,11,13],存储示意图:

执行 a.pop() 后删除最后一个元素:

列表删除后:

接下来在索引 3 处增加一个元素 8,a.insert(3,8),插入后如下:

因此,对列表而言,因为它能增加或删除元素,所以它是可变的。
但是,如果仅仅在列表 a 中做这一步操作:
1 | #在 a[2](也是一个列表)中插入元素 6 |
插入后可视化图:

对于可变这个概念而言,就是真正调整a为可变的操作。
元组不可变流程查看
tuple 就是一个典型的不可变容器对象,它也没有append(),insert()这样的方法对它而言,同样也可以修改嵌套对象的取值,但这并没有真正改变 tuple 内的元素。
创建一个元组
a = (1,3,[5,7],9,11,13),存储示意图:
下面在元组中的列表插入一个元素6,可以看到,a 内元素没增没减,长度还是 6:

这就是不可变对象的本质,元组一旦创建后,长度就被唯一确定。
但是,对于 list 而言,列表长度会有增有减,所以它是可变的。