JEPA4Japan · 教程

列表、元组与序列思维

1,589字 5分钟阅读 #Python

存储有序数据,并理解标识、别名、可变性、复制、解包和序列操作。

课程进度 课程大纲 已发布 24/24 课

列表就像托盘;元组就像密封的卡片

有时,一个值是不够的。你可能需要多个学习主题、七天的每日分数,或一条 (day, minutes) 记录。Python 为有序的数据组提供了两种实用的形式。

  1. 列表 项目可以移入和移出
  2. 元组 各个位置组成一条固定记录
  3. 序列 按顺序读取项目
使用列表表示会发生变化的集合,使用元组表示结构固定的记录。

两者都是序列。其中的项目会保持顺序,从位置 0 开始,并且都可以使用 len()、in、索引和切片。

topics = ["Strings", "Lists", "Dictionaries"]

print(len(topics))
print(topics[0])
print(topics[-1])
print(topics[1:3])
print("Lists" in topics)
3
Strings
Dictionaries
['Lists', 'Dictionaries']
True

切片会在结束位置之前停止,并创建一个新的外层序列。访问一个不存在的索引会引发 IndexError;切片则可以安全地超出末尾。

列表可以增长和缩短

列表使用方括号,并且是可变的:其中的内容可以改变。

queue = ["Strings", "Lists"]
queue.append("Tuples")
queue.extend(["Dictionaries", "Sets"])
queue.insert(1, "Review")

print(queue)
['Strings', 'Review', 'Lists', 'Tuples', 'Dictionaries', 'Sets']

append(x) 添加一个项目。extend(things) 添加 things 中的每一个项目。追加 ["A", "B"] 会创建一个嵌套列表;用它扩展列表则会添加两个字符串。

要按值删除项目,请使用 remove()。要按位置删除项目并保留被删除的值,请使用 pop():

queue = ["Strings", "Review", "Lists"]
queue.remove("Review")
finished = queue.pop(0)

print(finished)
print(queue)
Strings
['Lists']

边界提醒:remove("missing") 会引发 ValueError,而对空列表调用 pop() 会引发 IndexError。如果没有找到项目是正常情况,请先用 in 检查,或者先确认列表不是空的。

两个名称标签可以指向同一个托盘

赋值不会复制列表。它只是在同一个托盘上再放一个名称标签。

  1. 一个列表 ["Strings", "Lists"]
  2. plan 第一个名称标签
  3. alias 第二个名称标签
  4. 修改一次 两个名称都能看到变化
alias = plan 共享同一个可变对象。
plan = ["Strings", "Lists"]
alias = plan
snapshot = plan.copy()

alias.append("Tuples")

print(plan)
print(snapshot)
print(alias is plan)
print(snapshot is plan)
['Strings', 'Lists', 'Tuples']
['Strings', 'Lists']
True
False

is 问的是“是不是完全同一个对象?”;== 问的是“内容是否相等?”copy() 或 plan[:] 会创建一个新的外层列表。这是浅拷贝,因此嵌套的可变项目仍然会被共享:

week = [["Mon", 20], ["Tue", 30]]
snapshot = week.copy()

week.append(["Wed", 40])
week[0][1] = 99

print(snapshot)
print(week)
[['Mon', 99], ['Tue', 30]]
[['Mon', 99], ['Tue', 30], ['Wed', 40]]

新的星期三托盘只存在于 week 中,但两个外层列表仍然指向同一个星期一内层托盘。

元组会保持一条记录的结构

元组通常使用圆括号。它们是不可变序列:没有 append(),没有 remove(),也不能给其中的项目重新赋值。

  1. 星期一 位置 0
  2. 45 分钟 位置 1
  3. 固定的二元组 这条记录始终包含两个部分
当每个位置都有长期固定的用途时,元组会很有用。
record = ("Monday", 45)
day, minutes = record

print(day)
print(minutes)
Monday
45

除非使用带星号的目标,否则解包时名称的数量必须与项目数量完全相同。只有一个项目的元组需要一个逗号:("Lists",) 是元组,而 ("Lists") 只是字符串。元组本身不能改变,但放在元组内部的可变列表仍然可以改变;元组并不是深度冷冻柜。

小项目:三天学习日志

创建 study_log.py。这个完整程序只使用 Python 内置工具。每天的记录是固定的元组;日志和主题队列则是列表。快照可以证明队列经过了复制,而不是创建了别名。

study_log = [
    ("Monday", 20),
    ("Tuesday", 35),
    ("Wednesday", 35),
]
topic_queue = ["Strings", "Lists", "Dictionaries"]

original_plan = topic_queue.copy()

finished = topic_queue.pop(0)
topic_queue.append("Sets")

total = 0
for day, minutes in study_log:
    total += minutes
    print(f"{day}: {minutes} minutes")

average = total / len(study_log)

print(f"Total: {total} minutes")
print(f"Average: {average:.1f} minutes")
print(f"Finished: {finished}")
print(f"Original: {original_plan}")
print(f"Now: {topic_queue}")
print(f"Snapshot safe: {original_plan == ['Strings', 'Lists', 'Dictionaries']}")

运行 python3 study_log.py,并检查结尾部分:

Monday: 20 minutes
Tuesday: 35 minutes
Wednesday: 35 minutes
Total: 90 minutes
Average: 30.0 minutes
Finished: Strings
Original: ['Strings', 'Lists', 'Dictionaries']
Now: ['Lists', 'Dictionaries', 'Sets']
Snapshot safe: True

这个程序始终有三条记录,因此进行除法是安全的。如果允许日志为空,那么在做除法之前,你需要先决定“平均值”应该表示什么。

三个小任务

  1. 追加还是扩展? 对 ["Tuples", "Sets"] 分别尝试这两种操作。预测哪个结果会出现嵌套。
  2. 复制侦探。 复制 [["Mon", 10]],然后修改内层的 10。解释为什么两个外层列表都能看到这一变化。
  3. 元组检查员。 创建 (topic, minutes, done),将它解包到三个名称中,然后打印一句话。

当你能做到以下这些时,就可以开始第 7 章了……

  • 你可以使用索引和切片读取序列中的项目;
  • 你可以在 append()、extend()、remove() 和 pop() 之间做出选择;
  • 你可以解释为什么别名会共享列表的变化;
  • 你可以进行浅拷贝,并说明它在嵌套列表上的边界;
  • 你可以在会变化的列表和结构固定的元组之间做出选择;
  • 你可以创建只有一个项目的元组,并正确地解包记录;
  • 你可以运行学习日志并看到 Snapshot safe: True。

接下来,你将用含义明确的字典键取代数字位置,并在需要“每种只保留一个”时使用集合。