(八)列表:保存和修改多条记录
1. 创建、访问与遍历
一周有多天阅读记录。给每一天单独起名字会重复许多代码,可以把它们按顺序放进列表:
# 方括号建立列表,每个位置保存一条分钟记录。
minutes = [20, 35, 15]
print(minutes[0], minutes[-1])
# for 每轮直接把一个元素绑定给 value,而不是把下标绑定给它。
total = 0
for value in minutes:
total += value
print("合计", total)输出 20 15 和 合计 70。下标、负下标与切片的读法与字符串相同。列表中的元素可以重新赋值,例如 minutes[1] = 40,修改第二条记录。
list(...) 也能根据一个可逐项读取的对象建立列表,例如 list(range(3)) 得到 [0, 1, 2]。这种能够逐项提供数据的对象叫可迭代对象;字符串、列表与 range 都可以交给 for。
2. 增删与返回结果
# append 把一个元素加到末尾,insert 在指定位置之前插入。
minutes = [20, 35, 15]
minutes.append(40)
minutes.insert(1, 10)
print(minutes)
# pop 删除对应位置的元素,并把被删除的值返回给调用者。
removed = minutes.pop(2)
print(removed, minutes)
# remove 按值删除第一次出现的位置。
minutes.remove(15)
print(minutes)输出:
[20, 10, 35, 15, 40]
35 [20, 10, 15, 40]
[20, 10, 40]
在下标 1 插入 10,原先从下标 1 开始的后续元素要移动到后面;删除下标 2 的 35 后,后续位置又向前补齐。因此中间插入删除的工作量,与需要移动的后续元素数量有关。
| 操作 | 作用 | 返回结果 |
|---|---|---|
a.append(x) |
末尾加入一个元素 | None |
a.extend(values) |
逐项把多个元素加入末尾 | None |
a.insert(i, x) |
在位置 i 前加入元素 |
None |
a.pop()、a.pop(i) |
删除末尾或指定位置 | 被删除的元素 |
a.remove(x) |
按值删除第一次匹配 | None |
a.index(x)、a.count(x) |
查第一个位置、统计出现次数 | 下标、次数 |
a.clear() |
清空列表内容 | None |
append([1, 2]) 加入的是一个列表元素,extend([1, 2]) 则加入两个整数。可以运行下面的对照:
# 两种动作分别使用独立列表,直接比较结构。
a = [0]
b = [0]
a.append([1, 2])
b.extend([1, 2])
print(a)
print(b)
# append 修改 a 本身,调用结果是 None。
result = a.append(3)
print(result, a)输出 [0, [1, 2]]、[0, 1, 2],最后一行是 None [0, [1, 2], 3]。若写 a = a.append(3),就会把名称 a 绑定到 None,失去原先通过它访问列表的方式。
准备按值删除时,可以直接给“找不到”的情况安排动作:
# 成员检查与删除使用同一个要查找的值。
titles = ["Python", "NumPy"]
target = "Django"
if target in titles:
titles.remove(target)
else:
print("清单中没有这本书")空列表上调用 pop() 会报 IndexError;程序需要取一项时,先用 if titles: 选择有数据的动作,空列表则显示“清单为空”。处理方式写在程序里,便于跟着实际输入检查。
3. 共享列表与独立副本
# b = a 复制的是对象引用,这两个名称先后指向同一列表。
a = [1, 2]
b = a
b.append(3)
print(a, b)
# 重新赋值让 b 指向新列表,a 继续指向原来的列表。
b = [9]
print(a, b)第一行输出 [1, 2, 3] [1, 2, 3],第二行输出 [1, 2, 3] [9]。关键是区分“修改对象”与“名称改为指向另一个对象”。
# copy 创建一个新的外层列表;这里的整数元素按原顺序进入副本。
a = [1, 2]
b = a.copy()
b.append(3)
print(a, b)
# is 比较身份,== 比较内容;两个独立列表可以内容相等。
print(a is b)
print(a == [1, 2])这里 b 是独立的外层列表。a[:]、list(a) 也可以建立浅拷贝。再看里面还套着列表的情况:
# 外层被复制,内层列表仍由两个外层共同引用。
a = [[1, 2], [3, 4]]
b = a.copy()
b[0].append(9)
print(a)
print(b)
# 替换 b 的第二个元素只修改 b 的外层。
b[1] = [8]
print(a)
print(b)第一次修改后,两者都是 [[1, 2, 9], [3, 4]]。替换后,a 仍为 [[1, 2, 9], [3, 4]],b 变为 [[1, 2, 9], [8]]。
想把这个嵌套结构的内部列表也复制,可以使用标准库 copy 中的 deepcopy:
# deepcopy 继续复制内部可变对象,得到独立的这份嵌套记录。
from copy import deepcopy
a = [[1, 2], [3, 4]]
b = deepcopy(a)
b[0].append(9)
print(a)
print(b)列表的 += 也会更新原对象,和重新赋值可以放在一起比较:
# 原地扩展仍修改共享的列表,因此 b 也能读到新加入的 3。
a = [1, 2]
b = a
a += [3]
print(a, b)
# + 建立新列表,再把 a 绑定过去;b 继续指向原列表。
a = a + [4]
print(a, b)第一行输出两个 [1, 2, 3];第二行中 a 是 [1, 2, 3, 4],b 仍是 [1, 2, 3]。所以不能把所有对象上的 += 都理解为创建副本再赋值,应沿着当前类型的动作观察对象是否改变。
*= 也要沿着对象类型观察。把数字和列表放在一起跟踪:
# 整数不能原地改成另一个数;number 更新绑定,old 继续保存原来的 3。
number = 3
old = number
number *= 2
print(number, old)
# 列表 *= 2 原地重复其中的元素,两个名称仍指向同一列表。
values = [1, 2]
alias = values
values *= 2
print(values, alias)
print(values is alias)依次输出 6 3、[1, 2, 1, 2] [1, 2, 1, 2]、True。数字一侧,number 重新指向计算结果 6,old 仍指向 3;列表一侧,原对象的内容变了,values 和 alias 都能看到重复后的四个元素。
[0] * 3 可以建立三个零,但 [[0] * 3] * 2 重复的是同一行列表的引用:修改第一行的一个位置,第二行观察到的也是那一行。*= 同样重复元素引用,不会为内部列表逐个创建副本。需要独立的行时,可以每轮新建一次列表;后面的推导式会把这个过程写成较短的形式。可变序列的更新操作
4. 切片赋值与 del 删除
列表的切片也能放在赋值左边,用一批新元素替换原来的一段位置:
# [1:3] 原来选中 20、30,新数据会替换这一段,长度可以随之变化。
minutes = [10, 20, 30, 40]
minutes[1:3] = [25, 35, 36]
print(minutes)
# del 删除指定位置或切片;删除后,后面的元素向前补位。
del minutes[0]
print(minutes)
del minutes[-2:]
print(minutes)输出 [10, 25, 35, 36, 40]、[25, 35, 36, 40]、[25, 35]。del minutes[0] 删除容器里的一个位置;del minutes 则移除当前名称绑定。第四章删除实例属性时,还会继续使用 del。
对切片读取和切片赋值分别跟踪:part = minutes[1:3] 建立一份新的外层列表,minutes[1:3] = ... 则改写原来的 minutes。它们的方括号相同,所在的赋值位置不同。
5. 遍历时删除造成的位置变化
准备去掉零分钟记录,直接遍历原列表并删除当前元素,会出现下面的结果:
# 错误示例:删除让后续元素前移,下一轮的位置却继续向后推进。
minutes = [0, 0, 20]
for value in minutes:
if value == 0:
minutes.remove(value)
print(minutes)输出 [0, 20],有一条零分钟记录留下来。第一轮删除下标 0,原下标 1 的零移到下标 0;迭代继续读取下标 1 时,读到的已经是 20。
可以保留原列表作为遍历来源,把需要的项加入另一份列表:
# 读取 minutes 的顺序保持稳定,只把保留项写到 kept。
minutes = [0, 0, 20]
kept = []
for value in minutes:
if value != 0:
kept.append(value)
print(kept)这次输出 [20]。第(十四)节的列表推导式会把同一套“读取、判断、保留”写得更紧凑。先理解数据与读取位置分别怎样变化,再选择写法。序列的修改与遍历