字典(Dict)
字典(dict)是 Python 中最重要的映射类型,存储键值对(key-value)。它通过哈希表实现,查找、插入、删除的平均时间复杂度都是 O(1)。从 Python 3.7 起,字典保证按插入顺序遍历,这让它不仅是查询利器,也常被当作有序映射使用。
创建字典
# 1. 花括号字面量
person = {"name": "Alice", "age": 30, "city": "北京"}
print(person) # {'name': 'Alice', 'age': 30, 'city': '北京'}
# 2. dict() 构造器
person2 = dict(name="Bob", age=25) # 关键字参数
person3 = dict([("name", "Carol"), ("age", 28)]) # 键值对列表
print(person2, person3)
# 3. 空字典
empty: dict = {}
also_empty = dict()
# 4. 字典推导式(详见后文)
squares = {x: x * x for x in range(4)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9}
:::tip 键的要求
字典的键必须是可哈希(hashable)对象——通常是不可变类型,如 str、int、float、tuple(其元素也要可哈希)。列表、字典、集合都不能做键。
:::
访问与修改
person = {"name": "Alice", "age": 30, "city": "北京"}
# 通过键访问
print(person["name"]) # Alice
# print(person["email"]) # KeyError 键不存在会报错
# 安全访问:get
print(person.get("email")) # None
print(person.get("email", "未填写")) # 未填写 提供默认值
# 修改/新增
person["age"] = 31 # 修改已存在的键
person["email"] = "a@x.com" # 新增键值对
print(person)
# 删除
del person["city"]
email = person.pop("email") # 删除并返回值
print(email, person)
# 成员判断 in(判断键,不是值)
print("name" in person) # True
print("age" not in person) # False
:::warning [] vs get
用 [] 访问不存在的键会抛 KeyError;用 get 则返回 None 或默认值。不确定键是否存在时优先用 get。
:::
keys / values / items
这三个方法分别返回字典的键视图、值视图、键值对视图。
scores = {"Alice": 90, "Bob": 85, "Carol": 95}
# 视图对象是动态的,会随字典变化
keys = scores.keys()
values = scores.values()
items = scores.items()
print(list(keys)) # ['Alice', 'Bob', 'Carol']
print(list(values)) # [90, 85, 95]
print(list(items)) # [('Alice', 90), ('Bob', 85), ('Carol', 95)]
# 遍历
for name, score in scores.items():
print(f"{name}: {score}")
# 视图随字典更新
scores["David"] = 88
print(list(keys)) # ['Alice', 'Bob', 'Carol', 'David'] 自动反映新键
:::info 遍历顺序
Python 3.7+ 字典保持插入顺序,因此 keys()/values()/items() 的遍历顺序与插入顺序一致。
:::
get 与 setdefault
get 用于安全读取,setdefault 用于"不存在则设置并返回,存在则直接返回"。
# 统计每个字母出现次数:传统写法
text = "abracadabra"
counts: dict[str, int] = {}
for ch in text:
if ch in counts:
counts[ch] += 1
else:
counts[ch] = 1
print(counts) # {'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}
# 用 setdefault 简化
counts2: dict[str, int] = {}
for ch in text:
counts2[ch] = counts2.setdefault(ch, 0) + 1
print(counts2)
:::tip 更 Pythonic 的做法
对于"按分组累积"这种模式,collections.defaultdict 比 setdefault 更优雅,详见后文。
:::
字典推导式
字典推导式用一行创建字典,语法与列表推导式类似。
# 1. 平方映射
squares = {x: x * x for x in range(6)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# 2. 反转键值
original = {"a": 1, "b": 2, "c": 3}
reversed_map = {v: k for k, v in original.items()}
print(reversed_map) # {1: 'a', 2: 'b', 3: 'c'}
# 3. 带条件过滤
scores = {"Alice": 90, "Bob": 55, "Carol": 95, "Dave": 48}
passed = {name: score for name, score in scores.items() if score >= 60}
print(passed) # {'Alice': 90, 'Carol': 95}
嵌套字典
字典的值可以是另一个字典,常用于表示树形或表格数据。
# 学生成绩表
gradebook: dict[str, dict[str, int]] = {
"Alice": {"math": 90, "english": 85},
"Bob": {"math": 78, "english": 92},
"Carol": {"math": 95, "english": 88},
}
# 访问
print(gradebook["Alice"]["math"]) # 90
# 遍历所有人所有科目
for name, subjects in gradebook.items():
total = sum(subjects.values())
print(f"{name} 总分: {total}")
# 安全访问嵌套(避免 KeyError)
def get_score(name: str, subject: str) -> int | None:
return gradebook.get(name, {}).get(subject)
print(get_score("Bob", "english")) # 92
print(get_score("Dave", "math")) # None
合并运算符 | (Python 3.9+)
Python 3.9 引入了字典的合并运算符 | 与原地合并 |=,比 update 更直观。
defaults = {"theme": "light", "font": "sans", "lang": "en"}
user_prefs = {"theme": "dark", "font": "serif"}
# | 生成新字典:右侧覆盖左侧相同键
merged = defaults | user_prefs
print(merged)
# {'theme': 'dark', 'font': 'serif', 'lang': 'en'}
# 原地合并
config = {"host": "localhost"}
config |= {"port": 8080, "host": "0.0.0.0"}
print(config) # {'host': '0.0.0.0', 'port': 8080}
# 等价于 update
config2 = {"host": "localhost"}
config2.update({"port": 8080, "host": "0.0.0.0"})
print(config2 == config) # True
:::tip 字典合并的优先级
a | b 中 b 的键优先;与 dict(a, **b) 行为一致,但 | 更通用(支持任意两个映射)。
:::
有序性(Python 3.7+)
从 Python 3.7 起,标准字典保证插入顺序,3.6 起的 CPython 实现就已如此。这意味着无需使用 collections.OrderedDict 即可获得稳定的遍历顺序。
d: dict[str, int] = {}
for key in ["banana", "apple", "cherry"]:
d[key] = len(key)
print(list(d.keys())) # ['banana', 'apple', 'cherry'] 保持插入顺序
# 删除后重新插入会移到末尾
del d["apple"]
d["apple"] = 5
print(list(d.keys())) # ['banana', 'cherry', 'apple']
:::info 何时仍用 OrderedDict
3.7+ 普通字典已有序,但 OrderedDict 仍有用武之地:①需要 move_to_end、popitem(last=True) 等顺序操作(如实现 LRU 缓存);②显式表达"顺序敏感"的语义;③需要与旧版本兼容。一般场景直接用 dict 即可。
:::
collections.defaultdict
defaultdict 在访问不存在的键时自动创建默认值,非常适合"分组累积"模式。
from collections import defaultdict
# 1. 按首字母分组单词
words = ["apple", "avocado", "banana", "apricot", "cherry", "blueberry"]
groups: dict[str, list[str]] = defaultdict(list)
for word in words:
groups[word[0]].append(word)
print(dict(groups))
# {'a': ['apple', 'avocado', 'apricot'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}
# 2. 计数
counts: dict[str, int] = defaultdict(int)
for ch in "abracadabra":
counts[ch] += 1
print(dict(counts)) # {'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}
:::warning defaultdict 的默认值工厂
defaultdict(list) 的默认值是 [],defaultdict(int) 的默认值是 0(int() 返回 0)。访问键会真的创建该键值对,而 get 不会。
:::
collections.Counter
Counter 专为计数设计,是统计频率的最佳工具。
from collections import Counter
text = "the quick brown fox jumps over the lazy dog the quick"
counter = Counter(text.split())
print(counter)
# Counter({'the': 3, 'quick': 2, 'brown': 1, 'fox': 1, ...})
# most_common: 取出现次数最多的 N 个
print(counter.most_common(2)) # [('the', 3), ('quick', 2)]
# 支持加减运算
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2) # Counter({'a': 4, 'b': 3}) 计数相加
print(c1 - c2) # Counter({'a': 2}) 计数相减(结果为 0 的键被丢弃)
# 与 defaultdict 对比:Counter 直接给可迭代对象
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
print(Counter(words)) # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
实战:词频统计
下面综合运用字典、defaultdict、Counter 与推导式,统计一篇文章的词频并排序输出 Top-N。
from __future__ import annotations
import re
from collections import Counter
def clean_text(text: str) -> list[str]:
"""转小写并切分出单词,去除标点。"""
return re.findall(r"[a-z]+", text.lower())
def count_with_dict(words: list[str]) -> dict[str, int]:
"""用普通字典 + setdefault 统计。"""
counts: dict[str, int] = {}
for word in words:
counts[word] = counts.setdefault(word, 0) + 1
return counts
def count_with_counter(words: list[str]) -> Counter:
"""用 Counter 一行搞定。"""
return Counter(words)
def main() -> None:
text = """
Python is a great language. Python is easy to learn.
Many people love Python. Python is powerful and flexible.
The python community is friendly and welcoming.
"""
words = clean_text(text)
# 方式 1:普通字典
counts1 = count_with_dict(words)
# 按频次降序排序
sorted1 = sorted(counts1.items(), key=lambda kv: (-kv[1], kv[0]))
print("普通字典 Top 5:")
for word, count in sorted1[:5]:
print(f" {word}: {count}")
# 方式 2:Counter
counts2 = count_with_counter(words)
print("\nCounter Top 5:")
for word, count in counts2.most_common(5):
print(f" {word}: {count}")
# 用推导式筛选频次 >= 2 的词
frequent = {w: c for w, c in counts2.items() if c >= 2}
print(f"\n频次 >= 2 的词: {frequent}")
if __name__ == "__main__":
main()
输出示意:
普通字典 Top 5:
python: 5
is: 5
and: 2
friendly: 1
flexible: 1
Counter Top 5:
python: 5
is: 5
and: 2
flexible: 1
friendly: 1
频次 >= 2 的词: {'python': 5, 'is': 5, 'and': 2}
:::tip Counter 的优势
对于"统计频次"这种典型场景,Counter 比手写字典循环更简洁、更可读,且自带 most_common 等实用方法,应作为首选。
:::
小结
- 字典是键值映射,基于哈希表,平均 O(1) 查找。
- 键必须可哈希(通常不可变);
[]访问不存在键会抛KeyError,用get更安全。 keys()/values()/items()返回动态视图,遍历按插入顺序(3.7+)。setdefault用于"无则设默认值",defaultdict是更优雅的替代。- Python 3.9+ 的
|/|=让字典合并更直观。 Counter是计数利器,most_common直接取 Top-N。- 嵌套字典用
.get(key, {})链式访问可避免KeyError。
下一节,我们学习用于去重与集合运算的——集合。