JEPA4Japan · 教程

字典与集合

1,744字 5分钟阅读 #Python

使用可哈希键建立查找关系、统计出现次数、检查成员关系并删除重复项。

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

字典就像贴着标签的抽屉

列表回答“位置 0 上有什么?”字典回答“minutes 抽屉里有什么?”每个抽屉都有一个唯一的键和一个值。

  1. 键 "topic" 是抽屉的名称
  2. 字典 找到对应的抽屉
  3. 值 里面装着 "Lists"
有意义的键会指向对应的值。
session = {
    "topic": "Lists",
    "minutes": 35,
    "completed": True,
}

print(session["topic"])
print(session["minutes"])
Lists
35

在现代 Python 中,字典会保留插入顺序,但它的主要用途是通过键进行查找,而不是通过编号位置查找,也不会自动排序。

打开、添加、更新和移除抽屉

方括号用于读取必需存在的键。赋值操作会添加新键,或者替换已有键所对应的值。

progress = {"Lists": 1, "Tuples": 1}
progress["Dictionaries"] = 1
progress["Lists"] = 2

print(progress)
print("Lists" in progress)
print("Sets" in progress)
{'Lists': 2, 'Tuples': 1, 'Dictionaries': 1}
True
False

对于字典,in 检查的是键,而不是值。使用方括号访问不存在的键会引发 KeyError。如果键可能不存在,可以使用 get() 明确提供默认值:

scores = {"Lists": 0}

print(scores.get("Lists", 0))
print(scores.get("Sets", 0))
print("Lists" in scores)
print("Sets" in scores)
0
0
True
False

第一个 0 是字典中存储的值;第二个表示键不存在。仅靠 get() 无法区分这两种含义。del mapping[key] 会移除一个确定存在的键。mapping.pop(key) 会移除该键并返回它的值。如果键不存在,这两种操作都可能失败,除非你先进行检查,或者有意为 pop() 提供默认值。

需要同时使用键和值时,可以遍历 items():

for topic, count in progress.items():
    print(f"{topic}: {count}")

keys() 用于遍历键,values() 用于遍历值。

键的形态必须保持稳定

字典使用哈希快速找到正确的抽屉。键必须是可哈希的:它的哈希值必须保持稳定,而且相等的键必须具有相同的哈希值。

  1. 字符串 "Lists"
  2. 数字 42
  3. 固定元组 (3, 5)
  4. 可变列表 [3, 5]
稳定且可哈希的值可以作为字典的键和集合的成员。

字符串、整数以及仅包含可哈希元素的元组,都是常见的键。列表、字典和集合是可变且不可哈希的,因此不能作为键。包含列表的元组同样不可哈希。字典的值可以是任意 Python 对象。

计数器非常适合使用 get(key, 0),因为键不存在实际上就意味着“出现了零次”:

counts = {}

for topic in ["Lists", "Sets", "Lists"]:
    counts[topic] = counts.get(topic, 0) + 1

print(counts)
{'Lists': 2, 'Sets': 1}

集合就像不允许重复贴纸的托盘

一个集合最多只保留一个相等且可哈希的成员。它适合处理唯一性、成员关系以及不同集合之间的关系,而不是维护有意义的显示顺序。

  1. 重复 两张相同的贴纸到来了
  2. 集合 只保留一个相等的成员
  3. 唯一 成员关系保持简单
集合关心的是某个值是否属于它,而不是这个值位于什么位置。
tags = {"python", "beginner", "python"}
empty_tags = set()

tags.add("collections")
tags.discard("missing")

print(len(tags))
print("python" in tags)
print(type(empty_tags).__name__)
3
True
set

{} 创建的是空字典,因此空集合必须写成 set()。当 x 不存在时,remove(x) 会引发 KeyError;而 discard(x) 会平静地接受“它已经不在了”这一情况。

集合运算可以回答一些很实用的问题:

planned = {"python", "collections", "testing"}
covered = {"python", "collections", "review"}

print((planned | covered) == {"python", "collections", "testing", "review"})
print((planned & covered) == {"python", "collections"})
print((planned - covered) == {"testing"})
print((planned ^ covered) == {"testing", "review"})
print({"python"} <= covered)

这五行都会输出 True。它们分别检查并集、交集、差集、对称差集和子集。应该直接比较集合,绝不要根据显示顺序来判断集合是否相等。如果你既需要唯一性,又需要保留首次出现的顺序,可以用集合记录哪些内容已经出现,再用列表按照顺序显示它们。

小项目:学习主题摘要

创建 topic_summary.py。这个完整程序只使用 Python 的内置集合类型。字典用来描述学习记录和统计主题次数;集合用来移除重复标签;列表用来保留首次出现时的显示顺序。

sessions = [
    {"topic": "Lists", "minutes": 35, "tags": ("python", "collections")},
    {"topic": "Dictionaries", "minutes": 40, "tags": ("python", "lookup")},
    {"topic": "Lists", "minutes": 25, "tags": ("python", "review")},
]

topic_counts = {}
seen_tags = set()
tag_order = []
total = 0

for session in sessions:
    topic = session["topic"]
    total += session["minutes"]
    topic_counts[topic] = topic_counts.get(topic, 0) + 1

    for tag in session["tags"]:
        if tag not in seen_tags:
            seen_tags.add(tag)
            tag_order.append(tag)

planned = {"python", "collections", "testing"}
missing = planned - seen_tags

print(f"Total: {total} minutes")
for topic, count in topic_counts.items():
    print(f"{topic}: {count} session(s)")
print(f"Tags: {', '.join(tag_order)}")
print(f"Missing planned tags: {len(missing)}")
print(f"Counts correct: {topic_counts == {'Lists': 2, 'Dictionaries': 1}}")

运行 python3 topic_summary.py,并检查输出:

Total: 100 minutes
Lists: 2 session(s)
Dictionaries: 1 session(s)
Tags: python, collections, lookup, review
Missing planned tags: 1
Counts correct: True

边界提醒:这里的每条学习记录都拥有所需的键,并且分钟数都是数字。缺失或类型错误的数据需要进行验证,我们会在第 10 章学习这一内容。

三个小任务

  1. 寻找零值。 使用 {"Lists": 0},并通过 in 证明值为零和键不存在是两种不同的情况。
  2. 单词计数器。 使用字典和 get(word, 0) 统计 ['read', 'code', 'read']。
  3. 计划比较器。 创建计划主题集合和已完成主题集合;找出两边都有的已完成主题,以及仍未完成的主题。

如果你已经做到以下几点,就可以开始第 8 章了……

  • 你可以读取、添加、更新并有意识地移除字典条目;
  • 你知道字典中的 in 检查的是键;
  • 你可以区分不存在的键和存储的零值;
  • 你可以解释为什么键和集合成员必须是可哈希的;
  • 你可以使用 set() 创建空集合,并相信它会保留唯一的成员;
  • 你可以使用五种基本的集合关系,而不依赖顺序;
  • 你可以运行主题摘要程序,并看到 Counts correct: True。

接下来,你将使用 enumerate()、zip()、sorted() 和易于阅读的推导式来转换这些集合。