Python 教程 / 字典和集合

字典和集合

Python中两种强大的数据结构:字典(dict)用于存储键值对,集合(set)用于存储无序唯一元素。两者都提供极快的查找速度。

为什么需要字典?

假设要存储学生的成绩信息,用两个list实现:

Python
# 用两个list存储姓名和成绩
names = ['Michael', 'Bob', 'Tracy']
scores = [95, 75, 85]

# 查找Tracy的成绩
index = names.index('Tracy')  # 需要遍历整个list
print(scores[index])           # 85 

这种方式的问题:list越长,查找越慢。因为需要从头遍历整个list才能找到目标元素的位置。

用字典实现,只需要一个"名字"到"成绩"的映射:

Python
# 直接通过名字查找成绩,无论字典多大,速度都一样快
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
print(d['Tracy'])  # 85 
dict查找速度快的秘密

dict内部使用哈希算法(Hash):给定一个key,立即能计算出value的存储位置,实现 O(1) 时间复杂度查找。

哈希算法原理图解

┌─────────────────────────────────────────────────────────────────────┐
│                        dict 哈希算法原理                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  假设要存储: d['Michael'] = 95                                       │
│                                                                     │
│  Step 1: 计算 key 的哈希值                                           │
│  ┌──────────────┐                                                   │
│  │ hash('Michael') ──▶ 0x1a2b3c4d  (内存地址)                        │
│  └──────────────┘                                                   │
│         │                                                           │
│         ▼                                                           │
│  Step 2: 直接跳转到该地址存取 value                                  │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │ 内存布局:                                                      │   │
│  │                                                              │   │
│  │  0x1a2b3c4d: ┌─────────┐                                       │   │
│  │             │   95    │  ◀── 直接取出!                         │   │
│  │             └─────────┘                                       │   │
│  │                                                              │   │
│  └──────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  关键点:                                                             │
│  • 无论字典有多大,给定key就能直接计算出value的位置                    │
│  • 查找速度不会随字典大小增加而变慢                                    │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘ 
重要规则

dict的key必须是不可变对象!因为哈希算法要求相同的key必须产生相同的哈希值,如果key可变,哈希值就会变化,dict就会找不到数据。

创建字典

Python
# 方法1: 使用花括号直接创建
empty_dict = {}
person = {
    "name": "张三",
    "age": 25,
    "city": "北京"
}

# 方法2: 使用 dict() 函数
# 从键值对列表创建
pairs = [["a", 1], ["b", 2]]
d = dict(pairs)           # {'a': 1, 'b': 2}

# 使用关键字参数创建
d = dict(name="李四", age=30)  # {'name': '李四', 'age': 30}

# 方法3: 使用 fromkeys() 创建空字典
d = dict.fromkeys(["a", "b", "c"], 0)  # {'a': 0, 'b': 0, 'c': 0} 
┌─────────────────────────────────────────────────────────────────────┐
│                     dict 内存结构示意图                               │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   person = {"name": "张三", "age": 25, "city": "北京"}              │
│                                                                     │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                     dict 内部结构                            │   │
│   │                                                             │   │
│   │   ┌─────────┬─────────┬─────────┐                            │   │
│   │   │  key    │  ──▶    │  value  │                            │   │
│   │   ├─────────┼─────────┼─────────┤                            │   │
│   │   │ "name"  │  ──▶    │ "张三"  │                            │   │
│   │   ├─────────┼─────────┼─────────┤                            │   │
│   │   │ "age"   │  ──▶    │   25    │                            │   │
│   │   ├─────────┼─────────┼─────────┤                            │   │
│   │   │ "city"  │  ──▶    │ "北京"  │                            │   │
│   │   └─────────┴─────────┴─────────┘                            │   │
│   │                                                             │   │
│   │   注意: key 必须是不可变类型 (str, int, tuple)                │   │
│   │         value 可以是任意类型                                 │   │
│   └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘ 

访问字典

Python
d = {"name": "张三", "age": 25, "city": "北京"}

# 直接访问 (如果key不存在会报错!)
print(d["name"])           # 张三
# print(d["job"])        # KeyError: 'job'

# 使用 get() 方法 (安全访问,不会报错)
print(d.get("age"))          # 25
print(d.get("job"))          # None (返回None,不报错)
print(d.get("job", "未知"))  # 未知 (提供默认值)

# 检查key是否存在
print("name" in d)          # True
print("job" in d)           # False 

字典操作

Python
person = {"name": "张三", "age": 25, "city": "北京"}

# 添加/修改元素
person["job"] = "工程师"      # 添加新键值对
person["age"] = 26          # 修改已存在的键
print(person)

# 删除元素
del person["city"]           # 删除指定键值对
age = person.pop("age")     # 删除并返回对应的value
print("删除的age:", age)

# popitem() 删除并返回最后一个键值对 (Python 3.7+)
item = person.popitem()
print("删除的项:", item)

# 清空字典
person.clear()
print(person)            # {} 
┌─────────────────────────────────────────────────────────────────────┐
│                    字典操作内存变化图                                 │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  初始状态:                                                           │
│   person = {"name": "张三", "age": 25, "city": "北京"}              │
│                                                                     │
│  person["job"] = "工程师" 后:                                        │
│   person = {"name": "张三", "age": 25, "city": "北京", "job": "工程师"}│
│                                                                     │
│  del person["city"] 后:                                             │
│   person = {"name": "张三", "age": 25, "job": "工程师"}               │
│                                                                     │
│  ⚠️ 注意: 字典的"顺序"在 Python 3.7+ 是插入顺序                       │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘ 

遍历字典

Python
d = {"name": "张三", "age": 25, "city": "北京"}

# 遍历所有key
for key in d:
    print(key)

# 遍历所有key (显式方式)
for key in d.keys():
    print(key)

# 遍历所有value
for value in d.values():
    print(value)

# 遍历所有键值对
for key, value in d.items():
    print(f"{key}: {value}")

# 字典长度
print(len(d))               # 3 
┌─────────────────────────────────────────────────────────────────────┐
│                    dict 遍历方法对比                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  方法              │ 返回类型        │ 用途                        │
│  ─────────────────┼────────────────┼───────────────────────────── │
│  d.keys()         │ dict_keys      │ 获取所有key                  │
│  d.values()       │ dict_values    │ 获取所有value                │
│  d.items()        │ dict_items     │ 获取所有键值对                │
│                                                                     │
│  for key in d:        # 最简洁,推荐使用                             │
│  for key in d.keys(): # 显式表明在遍历key                           │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘ 

list 与 dict 对比

特性 list dict
数据结构 有序序列 键值对映射
查找方式 通过索引 O(n) 通过键 O(1)
查找速度 随长度增加而变慢 始终很快
存储空间 节省内存 占用内存较多
使用场景 有序数据、需要索引 快速查找、键值映射
表示方式 [1, 2, 3] {'a': 1, 'b': 2}

集合基础

集合(set)是一组无序且不重复的元素集合。可以把它看作只有key没有value的dict。

Python
# 创建集合
empty_set = set()     # 注意: {} 创建的是空字典,不是空集合!
nums = {1, 2, 3, 4, 5}

# 自动去重
unique_nums = {1, 2, 2, 3, 3, 3}
print(unique_nums)        # {1, 2, 3}

# 使用 set() 从其他序列创建
letters = set("hello")
print(letters)            # {'h', 'e', 'l', 'o'} (l只保留一个)

# 从list创建集合 (常用去重方法)
duplicates = [1, 2, 2, 3, 3, 3]
unique = set(duplicates)
print(unique)             # {1, 2, 3} 
┌─────────────────────────────────────────────────────────────────────┐
│                     set 特性示意图                                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  s = {1, 2, 3, 4, 5}                                                │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │                      set 内部结构                           │    │
│  │                                                             │    │
│  │   {1, 2, 3, 4, 5}  ──▶  [位置由哈希算法决定]                  │    │
│  │                                                             │    │
│  │   特性:                                                      │    │
│  │   ✓ 无序 (不按插入顺序存储)                                   │    │
│  │   ✓ 不重复 (自动去重)                                         │    │
│  │   ✓ 需要可变对象作为元素 (但set本身不可变)                    │    │
│  │                                                             │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘ 

集合运算

┌─────────────────────────────────────────────────────────────────────┐
│                     集合运算图解                                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   A = {1, 2, 3, 4}          B = {3, 4, 5, 6}                       │
│                                                                     │
│   ┌─────────────────┐    ┌─────────────────┐                       │
│   │  A               │    │  B               │                       │
│   │  1  2  3  4      │    │  3  4  5  6      │                       │
│   └─────────────────┘    └─────────────────┘                       │
│                                                                     │
│   并集 (A | B): {1, 2, 3, 4, 5, 6}  ─ 合并两个集合的所有元素          │
│   交集 (A & B): {3, 4}            ─ 两个集合都有的元素               │
│   差集 (A - B): {1, 2}            ─ 只在A中的元素                    │
│   对称差集 (A ^ B): {1, 2, 5, 6}   ─ 不同时在A和B中的元素             │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘ 
Python
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

# 并集: 合并两个集合的所有元素
print(a | b)              # {1, 2, 3, 4, 5, 6}
print(a.union(b))        # {1, 2, 3, 4, 5, 6}

# 交集: 两个集合都有的元素
print(a & b)              # {3, 4}
print(a.intersection(b)) # {3, 4}

# 差集: 只在第一个集合中的元素
print(a - b)              # {1, 2}
print(a.difference(b))    # {1, 2}

# 对称差集: 不同时在两个集合中的元素
print(a ^ b)              # {1, 2, 5, 6}
print(a.symmetric_difference(b))  # {1, 2, 5, 6} 

子集与超集

Python
a = {1, 2, 3}
b = {1, 2, 3, 4, 5}
c = {1, 2}

# 判断子集: c 是 a 的子集吗?
print(c.issubset(a))            # True
print(c <= a)                   # True

# 判断超集: a 是 c 的超集吗?
print(a.issuperset(c))            # True
print(a >= c)                    # True

# 判断真子集/真超集
print(a < b)                     # True (a是b的真子集)
print(a < a)                     # False (不是真子集)
print(a <= a)                    # True (是子集,但不是真子集)

# 判断是否不相交
print({1, 2}.isdisjoint({3, 4}))  # True
print({`1`, `2`}.isdisjoint({2, 3}))  # False (有交集2) 

集合方法

Python
s = {1, 2, 3}

# 添加元素
s.add(4)                 # 添加单个元素
s.update([5, 6])         # 添加多个元素 (传入可迭代对象)
print(s)                   # {1, 2, 3, 4, 5, 6}

# 删除元素
s.remove(1)               # 删除指定元素,不存在会报错 KeyError
s.discard(10)             # 删除指定元素,不存在不会报错
removed = s.pop()             # 随机删除并返回一个元素
print("popped:", removed)

# 清空集合
s.clear()
print(s)                   # set()

# 集合长度
s = {1, 2, 3}
print(len(s))               # 3

# 复制集合
s_copy = s.copy()
print(s_copy)             # {1, 2, 3} 

可变对象与不可变对象

在Python中,可变对象(如list、dict、set)可以在原地修改;不可变对象(如str、int、tuple)修改后会创建新对象。

┌─────────────────────────────────────────────────────────────────────┐
│                  可变对象 vs 不可变对象                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  不可变对象 (str, int, float, tuple):                               │
│  ┌─────────────────────────────────────────────────────────────┐  │
│  │                                                             │  │
│  │   a = 'abc'                                                 │  │
│  │   b = a.replace('a', 'A')  # replace返回新字符串             │  │
│  │                                                             │  │
│  │   ┌───┐     ┌───────┐                                       │  │
│  │   │ a │────▶│ 'abc' │                                       │  │
│  │   └───┘     └───────┘                                       │  │
│  │   ┌───┐     ┌───────┐                                       │  │
│  │   │ b │────▶│ 'Abc' │  ◀── 新创建的对象                      │  │
│  │   └───┘     └───────┘                                       │  │
│  │                                                             │  │
│  │   a 的值不变,b 是新对象                                     │  │
│  └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│  可变对象 (list, dict, set):                                        │
│  ┌─────────────────────────────────────────────────────────────┐  │
│  │                                                             │  │
│  │   a = ['c', 'b', 'a']                                       │  │
│  │   a.sort()                 # 原地修改                        │  │
│  │                                                             │  │
│  │   ┌───────────────────┐                                     │  │
│  │   │ a ────────────────▶ ['a', 'b', 'c']                     │  │
│  │   │        (同一个对象,内容被修改)                          │  │
│  │   └───────────────────┘                                     │  │
│  │                                                             │  │
│  │   a 指向的对象被直接修改                                     │  │
│  └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘ 
为什么dict的key不能是可变对象?

因为dict用key的哈希值来计算存储位置。如果key是可变的,每次计算出的哈希值可能不同,dict就无法正确找到对应的value了。

Python
# 可变对象不能作为dict的key
key = [1, 2, 3]
d = {}
try:
    d[key] = "value"
except TypeError as e:
    print(f"错误: {e}")

# tuple可以作为dict的key (因为tuple不可变)
t = (1, 2, 3)
d2 = {t: "value"}
print(d2[t])              # value

# 但包含可变对象的tuple不能作为key
t2 = (1, [2, 3])
try:
    d2[t2] = "error"
except TypeError as e:
    print(f"错误: {e}") 

章节测验

问题1: 以下哪个可以作为dict的key?

问题2: 下面代码的输出是什么?

d = {'a': 1, 'b': 2}
print(d.get('c', 0)) 

问题3: 两个set A={1,2,3}和B={2,3,4},A|B的结果是?

问题4: dict查找速度快的原理是什么?

练习题

练习1: 统计单词出现频率

给定一个字符串,统计每个字符出现的次数(忽略大小写)。

输入: "Hello World"
输出: {'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1} 
练习2: 找出共同好友

有两个用户的的好友列表,找出他们的共同好友。

user1_friends = {"Alice", "Bob", "Charlie", "David"}
user2_friends = {"Bob", "David", "Eve", "Frank"}
输出: {'Bob', 'David'} 
练习3: 合并两个字典

合并两个字典,如果key冲突,使用第二个字典的值。

d1 = {'a': 1, 'b': 2}
d2 = {'b': 3, 'c': 4}
输出: {'a': 1, 'b': 3, 'c': 4} 
🐍 Python 在线代码编辑器
📝 运行结果