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 类型做 keystr/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. 对比辨析表

操作返回是否修改原 dictkey 不存在时
d[k]valueKeyError
d.get(k)value 或 None返回 None
d.get(k, default)value 或 default返回 default
d.setdefault(k, v)value是(不存在则插入)插入并返回 v
d.pop(k)valueKeyError
d.pop(k, default)value返回 default
del d[k]NoneKeyError

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 参数解包机制