完整讲解 · 8 段教学·配 7 道练习题·预计 30 分钟
本页是本章的通读版,可直接读完全部讲解。想动手写代码、跑判分,去 闯关模式。
上一节学完了 List 与 Tuple。这一节再学两种容器类型——
学完这两种,再加上前面的 list 和 tuple,Python 最常用的数据结构就齐了。
上一章节,我们学习了列表(List)和元组(tuple)来表示有序集合。
而我们在讲列表时,用 list 来存储用户的姓名:
name = ['一点水', '两点水', '三点水', '四点水', '五点水']那么如果我们为了方便联系这些童鞋,要把电话号码也添加进去,该怎么做呢?
用 list 可以这样子解决:
name = [['一点水', '131456780001'], ['两点水', '131456780002'], ...]但是这样很不方便。如果用列表来存储这些,列表越长,我们查找起来耗时就越长。
这时候就可以用 dict(字典)来表示了。Python 内置了字典(dict),全称 dictionary,使用键-值(key-value) 存储,具有极快的查找速度(如果学过 Java,dict 就相当于 Java 中的 Map)。
name = {'一点水': '131456780001', '两点水': '131456780002', '三点水': '131456780003'}字典是另一种可变容器,且可存储任意类型对象。
字典的每个键值(key: value)对用冒号 : 分割,每个对之间用逗号 , 分割,整个字典包括在花括号 {} 中:
d = {key1: value1, key2: value2}注意:键必须是唯一的(重复会以最后一个为准),值可以重复。键必须是不可变类型(数字、字符串、tuple 都行,list 不行)。
dict1 = {'liangdianshui': '111111', 'twowater': '222222', '两点水': '333333'}
dict2 = {'abc': 1234, 1234: 'abc'}
print(dict1)
print(dict2)我们用 dict 来存了通讯录,怎么按名字快速查到电话呢?直接用中括号 + key:
name = {'一点水': '131456780001', '两点水': '131456780002', '三点水': '131456780003'}
print(name['两点水'])输出的结果:
131456780002
可以看到,如果你知道某个人的名字(key),就能很快查到他对应的电话号码(value)。
⚠️ 注意:如果字典中没有这个键,直接
dict[key]会报错(KeyError)。如果不确定 key 是否存在,更安全的写法是dict.get(key)——找不到时返回None,不报错。
name.get('六点水') # None,不会报错
name['六点水'] # KeyError!向字典添加新内容、修改值,都是用 key 直接赋值:
dict1 = {'liangdianshui': '111111', 'twowater': '222222', '两点水': '333333'}
print(dict1)
# 新增一个键值对
dict1['jack'] = '444444'
print(dict1)
# 修改键值对(key 已存在时会覆盖)
dict1['liangdianshui'] = '555555'
print(dict1)输出的结果:
{'liangdianshui': '111111', 'twowater': '222222', '两点水': '333333'}
{'liangdianshui': '111111', 'twowater': '222222', '两点水': '333333', 'jack': '444444'}
{'liangdianshui': '555555', 'twowater': '222222', '两点水': '333333', 'jack': '444444'}
通过 del 可以删除某个 key 对应的元素,clear() 可以清空整个字典:
dict1 = {'liangdianshui': '111111', 'twowater': '222222', '两点水': '333333'}
# 通过 key 值,删除对应的元素
del dict1['twowater']
print(dict1)
# 删除字典中的所有元素
dict1.clear()
print(dict1)输出的结果:
{'liangdianshui': '111111', '两点水': '333333'}
{}
| 方法/函数 | 描述 |
|---|---|
len(d) | 元素个数 |
d.keys() | 所有 key |
d.values() | 所有 value |
d.items() | 所有 (key, value) 对 |
d.get(key) | 安全取值,找不到返回 None |
d.get(key, default) | 找不到返回 default |
d.pop(key) | 删除并返回 key 对应的值 |
d.clear() | 清空 |
|各位童鞋,前面咱们讲了 dict 的增、删、改、查。这里再补一个 Python 3.9 之后才出现的好东西—— dict 合并运算符 | 和 |=。
我们先来看一个需求:水哥手上有两份联系人字典,需要把它们合并成一份。
old_contacts = {'一点水': '131456780001', '两点水': '131456780002'}
new_contacts = {'三点水': '131456780003', '两点水': '131456780099'}注意 两点水 这个 key 在两份里都有,我们希望以「新的」为准。
在 Python 3.9 之前,常见的写法是这样:
merged = {**old_contacts, **new_contacts}这种 ** 解包写法没毛病,但是不够直观。Python 3.9 之后,可以直接用 |:
merged = old_contacts | new_contacts
print(merged)输出的结果:
{'一点水': '131456780001', '两点水': '131456780099', '三点水': '131456780003'}
是不是发现,跟 set 的并集 | 长得一模一样?没错,作者就是这个意思——「把两个 dict 合并起来」。
如果你不想生成新字典,而是直接「在原字典上」合并新内容,那就用 |=:
old_contacts |= new_contacts
print(old_contacts)注意:| 是「右边覆盖左边」,跟 {**a, **b} 的逻辑一致。各位用的时候要留意这一点,别把哪边是新的、哪边是旧的搞反了。
从 Python 3.7 开始,dict 保留插入顺序——这是语言级保证:
d = {}
d['c'] = 3
d['a'] = 1
d['b'] = 2
for key in d:
print(key)输出:
c
a
b
遍历的顺序就是当初插入的顺序。所以现在大多数情况下,你不再需要 import collections.OrderedDict,普通的 dict 就够用。
Python 的 set 和其他语言类似,是一个无序、不重复元素的集合。基本功能包括关系测试和消除重复元素。
set 和 dict 类似,但是 set 不存 value,只存 key。
创建一个 set,传一个 list 进去:
set1 = set([123, 456, 789])
print(set1)输出(顺序可能不一样,因为 set 是无序的):
{456, 123, 789}
注意:set 用大括号 {},跟 dict 一样。但是 dict 是 {key: value},set 是 {value, value, value},结构不一样。
⚠️ 空 set 不能写成
{}—— 那会创建一个空 dict。空 set 必须用set():python到闯关页运行这段 →empty_set = set() # ✓ 空 set empty_dict = {} # ✗ 这是空 dict
set 最好用的特性之一:自动去重。
set1 = set([123, 456, 789, 123, 123])
print(set1)输出:
{456, 123, 789}
输入有 5 个元素,输出只有 3 个——重复的 123 被自动丢掉了。
不过你也发现了:set 是无序的,打印顺序和放进去的顺序对不上,而且每次可能都不一样。想要一个顺序稳定的结果,可以用内置函数 sorted()——它接收任何可遍历的东西(set、list 都行),返回一个从小到大排好序的新 list:
set1 = set([123, 456, 789, 123, 123])
print(sorted(set1))输出:
[123, 456, 789]
「先用 set 去重,再用 sorted 排序」是很常用的组合,马上的练习就会用到。
通过 add(key) 方法可以添加元素到 set 中。重复添加同一个值不会有效果(因为 set 自动去重)。
set1 = set([123, 456, 789])
print(set1)
set1.add(100)
print(set1)
set1.add(100) # 再添加一次 100
print(set1)输出:
{456, 123, 789}
{456, 123, 100, 789}
{456, 123, 100, 789}
通过 remove(key) 方法可以删除 set 中的元素:
set1 = set([123, 456, 789])
print(set1)
set1.remove(456)
print(set1)输出:
{456, 123, 789}
{123, 789}
⚠️ 如果
remove一个不存在的元素会报错。如果不确定元素是否存在,用discard—— 找不到也不会报错。
因为 set 是一个无序不重复元素集,所以两个 set 可以做数学意义上的 并集(union)、交集(intersection)、差集(difference) 等操作。
| 运算 | 符号 | 含义 |
|---|---|---|
| 交集 | a & b | a 和 b 都有的元素 |
| 并集 | a | b | a 和 b 任一有的元素(去重) |
| 差集 | a - b | a 有但 b 没有的元素 |
| 对称差 | a ^ b | 仅在 a 或仅在 b 中的元素(不要两个都有的) |
例子:
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a & b) # 交集
print(a | b) # 并集
print(a - b) # 差集
print(a ^ b) # 对称差输出(顺序可能不同):
{3, 4}
{1, 2, 3, 4, 5, 6}
{1, 2}
{1, 2, 5, 6}
最后这一点很重要。我们做「成员判断」(x in collection)的时候:
collection 是 list,复杂度是 O(n)——元素越多越慢collection 是 set(或 dict),复杂度接近 O(1)——跟元素数量基本无关所以,如果代码里有大量「这个东西在不在那一堆里」的判断,把 list 转成 set 性能会大幅提升:
names_list = [f'user_{i}' for i in range(100000)]
names_set = set(names_list)
# 这两种结果一样,但是后者快得多
print('user_99999' in names_list)
print('user_99999' in names_set)set 的价值不光是去重,更重要的是快查。
读完了?动手练一遍才算真会。
去闯关模式练习 →