跳到主要内容

字典(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)对象——通常是不可变类型,如 strintfloattuple(其元素也要可哈希)。列表、字典、集合都不能做键。 :::

访问与修改

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.defaultdictsetdefault 更优雅,详见后文。 :::

字典推导式

字典推导式用一行创建字典,语法与列表推导式类似。

# 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 | bb 的键优先;与 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_endpopitem(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})

实战:词频统计

下面综合运用字典、defaultdictCounter 与推导式,统计一篇文章的词频并排序输出 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

下一节,我们学习用于去重与集合运算的——集合