Python dict 与 zip/unzip (Dictionary Basics)
1. 定义
dict 是 Python 的键值对映射类型,基于哈希表实现,平均 O(1) 查找。
zip 是把多个可迭代对象”按位配对”打包成元组序列的内置函数;“unzip”则借助 zip(*) 反向解包。
形象记法:dict 是”带标签的抽屉”,每个标签(key)唯一对应一个抽屉里的东西(value)。zip 是把几条队伍按位”拉拉链”——第 1 个和第 1 个一组,第 2 个和第 2 个一组。
2. 核心概念
| 概念 | 含义 | 备注 |
|---|---|---|
| key 不可变 | 只能用 hashable 类型做 key | str/int/tuple 可以,list/dict 不行 |
| 唯一性 | 同一 key 后写覆盖先写 | 不会报错 |
| 插入顺序 | Python 3.7+ 保证按插入顺序遍历 | 之前的 dict 也大多如此,3.7 才成规范 |
| 哈希表 | dict 底层结构 | 查找/插入/删除平均 O(1) |
zip 严格配对 | 按最短的可迭代对象截断 | 想不截断用 itertools.zip_longest |
3. dict 基本命令
# ── 创建 ──
d = {"a": 1, "b": 2}
d = dict(a=1, b=2) # 关键字参数写法
d = dict([("a", 1), ("b", 2)]) # 从键值对列表创建
d = {}.fromkeys(["a", "b"], 0) # 多个同值 key → {'a': 0, 'b': 0}
# ── 访问 ──
d["a"] # key 不存在会抛 KeyError
d.get("c") # 不存在返回 None,不报错
d.get("c", 0) # 不存在返回默认值 0
# ── 增删改 ──
d["c"] = 3 # 新增 / 覆盖
del d["a"] # 删除 key,不存在抛 KeyError
d.pop("b") # 删除并返回 value
d.pop("x", None) # 带默认值,删除不存在也不报错
d.update({"e": 5}) # 批量更新
# ── 遍历 ──
for k in d: # 只遍历 key
for k, v in d.items(): # 同时遍历 key 和 value
list(d.keys()) # 所有 key
list(d.values()) # 所有 value
list(d.items()) # [('a', 1), ('b', 2)]
# ── 推导式 ──
{s: len(s) for s in ["hi", "world"]} # {'hi': 2, 'world': 5}
# ── 字典合并 (Python 3.9+) ──
merged = {"a": 1} | {"b": 2} # {'a': 1, 'b': 2}4. zip / unzip 详解
# ── zip:按位配对 ──
keys = ["a", "b", "c"]
vals = [1, 2, 3]
pairs = list(zip(keys, vals)) # [('a', 1), ('b', 2), ('c', 3)]
# 最经典用法:两个 list 直接拼成 dict
d = dict(zip(keys, vals)) # {'a': 1, 'b': 2, 'c': 3}
# 长度不一致:按最短截断
list(zip([1, 2, 3], ["a", "b"])) # [(1, 'a'), (2, 'b')]
# 不想截断:用 itertools
from itertools import zip_longest
list(zip_longest([1, 2, 3], ["a"], fillvalue="?"))
# [(1, 'a'), (2, '?'), (3, '?')]
# ── unzip:用 zip(*) 反向解包 ──
pairs = [("a", 1), ("b", 2), ("c", 3)]
keys, vals = zip(*pairs)
# keys = ('a', 'b', 'c')
# vals = (1, 2, 3)
# 原理:* 把列表拆成多个独立参数传给 zip
# 等价于 zip(("a",1), ("b",2), ("c",3))
# 按位配对 → 第 1 组全是 key,第 2 组全是 value
# ── 解包 dict:分别拿到 keys 和 values ──
d = {"a": 1, "b": 2}
k, v = zip(*d.items()) # k=('a','b'), v=(1,2)
# ── 转置二维矩阵 ──
matrix = [(1, 2, 3), (4, 5, 6)]
transposed = list(zip(*matrix)) # [(1, 4), (2, 5), (3, 6)]一句话记法:
zip(*x)是zip的逆运算。先zip(a, b)得到对,再zip(*对)就能拆回 a 和 b。
5. 对比辨析表
| 操作 | 返回 | 是否修改原 dict | key 不存在时 |
|---|---|---|---|
d[k] | value | 否 | 抛 KeyError |
d.get(k) | value 或 None | 否 | 返回 None |
d.get(k, default) | value 或 default | 否 | 返回 default |
d.setdefault(k, v) | value | 是(不存在则插入) | 插入并返回 v |
d.pop(k) | value | 是 | 抛 KeyError |
d.pop(k, default) | value | 是 | 返回 default |
del d[k] | None | 是 | 抛 KeyError |
6. 典型工作流
# 场景 1:反转 dict(key ↔ value)
d = {"a": 1, "b": 2}
inverted = {v: k for k, v in d.items()} # {1: 'a', 2: 'b'}
# 场景 2:按 value 排序
sorted_d = dict(sorted(d.items(), key=lambda x: x[1], reverse=True))
# 场景 3:计数(更优雅的是 collections.Counter)
words = ["a", "b", "a", "c", "b", "a"]
count = {}
for w in words:
count[w] = count.get(w, 0) + 1
# 场景 4:两个并行 list 同步遍历
names = ["Alice", "Bob"]
ages = [25, 30]
for name, age in zip(names, ages):
print(name, age)7. 常见误区
- ❌ “dict 的 key 可以是 list” → 错,list 不可哈希;要用 tuple 这种不可变类型
- ❌ “
zip会自动补齐长度” → 错,默认按最短截断,要补齐用zip_longest - ❌ “
zip(*x)是新函数” → 错,它就是zip+ 参数解包*,没有专门的 unzip 函数 - ❌ “Python 3.6 之前 dict 无序是 bug” → 错,那是规范允许的实现自由,3.7 才把”保序”写进语言规范
- ❌ “
d.get(k)找不到 key 会报错” → 错,它正是为了避免KeyError才返回 None
8. 延伸阅读 / 关联概念
collections.defaultdict— 带”工厂函数”默认值的 dict,省去setdefault的样板代码collections.Counter— 专门做计数的 dict 子类,自带.most_common(n)collections.OrderedDict— 3.7 之前需要它来保序;现在大多场景普通 dict 已够用- 哈希表原理 — 解释了为什么 key 必须 hashable、为什么平均 O(1)
*args/**kwargs— 理解zip(*x)的关键,是 Python 参数解包机制