1. 核心语义与定位
Python 字典(dict)是一种映射类型,它实现了键(key)到值(value)的对应关系,属于可变容器。字典在 Python 中扮演着极其重要的角色,其核心语义如下:
- 映射协议:字典存储的是“键 → 值”的关联,每个键在字典中唯一,通过键可以快速获取对应的值。
- 键的唯一性与可哈希性:键必须是可哈希的(即实现了
__hash__方法且哈希值在其生命周期内不变),并且在整个字典中不可重复。值则可以是任意 Python 对象,包括可变或不可变对象。 - 可变性:字典支持动态增、删、改操作,大小和内容可以随时改变。
- 引用语义:字典中保存的是对象的引用,而不是对象的拷贝。因此,如果值是一个可变对象(如列表),修改该对象会反映在字典中。
- 插入顺序保证:自 Python 3.7 起,字典会保留键值对的插入顺序,这是语言层面的正式保证(在 3.6 中只是 CPython 的实现细节)。这意味着遍历字典、
keys()、values()、items()等操作都会按照插入顺序进行。 - 与其他容器的区别:
- 与序列(如列表、元组)不同,字典不是通过整数下标访问,而是通过任意可哈希的键访问。
- 与集合(
set)相比,集合只存储键(元素),而字典存储键和值的映射。两者都基于哈希表实现,但字典多存储了一个值引用。 - 字典的查找、插入、删除操作在平均情况下时间复杂度为 O(1),远快于线性查找。
字典是 Python 中最常用的内置数据结构之一,广泛用于配置管理、缓存、数据聚合、对象属性存储等场景。
2. 创建字典
创建字典有多种方式,可以根据数据来源和需求选择最合适的一种。
2.1 字面量
最直观的方式,使用花括号 {} 直接定义:
d = {'name': 'Alice', 'age': 30}2.2 构造函数 dict()
dict() 可以接受多种形式的参数:
-
关键字参数:键必须是合法的标识符(字符串形式):
d = dict(name='Alice', age=30) # {'name': 'Alice', 'age': 30} -
可迭代对象:每个元素是一个包含两个元素的可迭代对象(如元组、列表),分别表示键和值:
d = dict([('name', 'Alice'), ('age', 30)]) # {'name': 'Alice', 'age': 30} -
映射对象:从另一个映射(如字典)创建:
original = {'a': 1, 'b': 2} d = dict(original) # 浅拷贝 -
混合使用:可以结合关键字参数和可迭代对象(但关键字参数优先级更高):
d = dict([('a', 1)], b=2) # {'a': 1, 'b': 2}
2.3 dict.fromkeys(iterable, value=None)
类方法,用于批量创建具有相同默认值的字典:
keys = ['a', 'b', 'c']
d = dict.fromkeys(keys, 0)
# {'a': 0, 'b': 0, 'c': 0}注意:如果 value 是可变对象(如列表),所有键会共享同一个对象引用,可能导致意外修改。
2.4 字典推导式
从可迭代对象生成字典,语法类似列表推导式:
d = {x: x ** 2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}可以添加条件过滤:
d = {k: v for k, v in [('a', 1), ('b', 2)] if v > 1}2.5 合并创建
- 使用
**解包合并多个字典:d1 = {'a': 1} d2 = {'b': 2} d = {**d1, **d2} # {'a': 1, 'b': 2} - Python 3.9+ 支持
|运算符:d = d1 | d2 # 新字典
3. 基本操作与时间复杂度
字典的核心操作均基于哈希表实现,平均时间复杂度为 O(1),但最坏情况下(大量哈希冲突)可能退化为 O(n)。
3.1 常用操作及其复杂度
| 操作 | 示例 | 平均时间复杂度 | 说明 |
|---|---|---|---|
| 访问元素 | d[key] | O(1) | 键不存在时抛出 KeyError |
| 安全访问 | d.get(key) | O(1) | 键不存在返回 None 或指定默认值 |
| 插入/更新 | d[key] = value | O(1) | 若键已存在则更新,否则插入 |
| 删除元素 | del d[key] | O(1) | 键不存在时抛出 KeyError |
| 弹出元素 | d.pop(key) | O(1) | 删除并返回值,可提供默认值 |
| 成员检测 | key in d | O(1) | 检查键是否存在 |
| 获取长度 | len(d) | O(1) | 字典内部维护元素个数 |
| 清空 | d.clear() | O(1) 或 O(n) | 通常直接释放内部表,视为 O(1) |
| 遍历 | for k in d | O(n) | 按插入顺序遍历所有键 |
| 复制 | d.copy() | O(n) | 浅拷贝 |
3.2 为什么是 O(1)?
字典内部使用哈希表:通过哈希函数计算键的哈希值,将其映射到内部数组的某个索引。查找时直接计算索引即可定位,因此与字典大小无关。但哈希冲突会降低效率,Python 使用开放寻址法解决冲突,并动态调整表大小以保持较低的负载因子(约 2/3),从而保证平均 O(1)。
4. 常用方法与分类
字典提供了丰富的方法,可分类为读取、写入、视图和复制等。
4.1 读取类方法
d[key]:直接访问,键不存在时抛出KeyError。d.get(key, default=None):安全访问,若键不存在返回default(默认为None),不会抛出异常。d.setdefault(key, default):若键存在则返回其值;若不存在,则插入key: default并返回default。常用于初始化嵌套结构。d = {} d.setdefault('list', []).append(1) # {'list': [1]}d.pop(key[, default]):删除键并返回其值,若键不存在且提供了default则返回default,否则抛出KeyError。d.popitem():删除并返回最后插入的键值对(Python 3.7+),以元组(key, value)形式返回。若字典为空则抛出KeyError。该操作可用于实现栈式弹出。
4.2 写入类方法
d[key] = value:直接赋值,插入或更新。d.update(other):将other中的键值对合并到当前字典,other可以是映射、可迭代的键值对序列或关键字参数。若键已存在则覆盖。d = {'a': 1} d.update({'b': 2}, c=3) # {'a': 1, 'b': 2, 'c': 3}d |= other(Python 3.9+):就地合并字典,等价于d.update(other),但仅接受字典类型。d.setdefault(key, default):兼具读取和写入功能,避免重复判断。
4.3 视图类方法
d.keys():返回字典所有键的动态视图。d.values():返回所有值的动态视图。d.items():返回所有键值对的动态视图,每个元素为(key, value)元组。
这些视图对象会动态反映字典的变化,支持迭代、成员检测和集合运算(详见第5部分)。
4.4 复制类方法
d.copy():返回字典的浅拷贝。新字典拥有独立的顶层结构,但嵌套的可变对象仍与原字典共享。import copy d = {'a': [1, 2]} shallow = d.copy() shallow['a'].append(3) # d['a'] 也会变为 [1,2,3]dict(d):等同于浅拷贝。copy.deepcopy(d):深拷贝,递归复制所有嵌套对象,完全独立。
5. 字典视图对象
keys()、values()、items() 返回的并不是列表,而是动态视图(dict_keys、dict_values、dict_items)。这些视图具有以下特性:
-
动态性:视图会实时反映字典的当前状态,即使字典后来被修改,视图也会随之更新。
d = {'a': 1} keys = d.keys() d['b'] = 2 print(keys) # dict_keys(['a', 'b']) -
可迭代:视图支持迭代,但不支持索引和切片。可以转换为列表或集合:
list(d.keys()) set(d.values()) -
成员检测:
key in d.keys()等价于key in d;value in d.values()检查值是否存在;(k, v) in d.items()检查键值对是否存在。 -
集合运算:
keys()和items()视图支持集合运算(如交集、并集、差集、对称差),因为它们的行为类似于集合,且键和键值对都是可哈希的。values()视图通常不支持集合运算,因为值可能不可哈希。d1 = {'a': 1, 'b': 2} d2 = {'b': 3, 'c': 4} d1.keys() & d2.keys() # {'b'} d1.items() & d2.items() # set(),因为值不同 d1.keys() - d2.keys() # {'a'} d1.items() | d2.items() # 合并所有键值对 -
视图的相等性:两个
dict_keys或dict_items视图相等当且仅当它们包含相同的元素(顺序无关),dict_values视图的相等性则比较元素内容和顺序(因为值可能重复且顺序有意义)。
8. 有序性与排序
8.1 插入顺序保证
Python 3.7+ 的字典保留插入顺序,这意味着:
- 遍历字典时,键值对按照插入的先后顺序出现。
list(d)、list(d.keys())、list(d.values())、list(d.items())均按插入顺序排列。- 删除后重新插入同一个键,该键会移动到末尾。
8.2 反向迭代
Python 3.8+ 支持 reversed(d),可以按插入顺序的逆序迭代键:
d = {'a': 1, 'b': 2, 'c': 3}
for key in reversed(d):
print(key) # c, b, a8.3 排序字典
普通字典本身不提供自动排序,但可以根据需要创建排序后的新字典:
- 按键排序:
sorted_dict = dict(sorted(d.items())) - 按值排序:
sorted_by_value = dict(sorted(d.items(), key=lambda item: item[1])) - 若需要保持字典始终有序(如按插入顺序或自定义顺序),可使用
collections.OrderedDict。
8.4 与 OrderedDict 的差异
虽然普通字典在 3.7+ 也保留顺序,但 OrderedDict 仍有一些特殊之处:
- 相等比较:普通字典的相等性不依赖顺序(
{'a':1,'b':2} == {'b':2,'a':1}为True),而OrderedDict的相等性依赖顺序。 - 额外方法:
OrderedDict提供move_to_end()等操作,可灵活调整顺序。 - 因此,在需要顺序敏感的比较或需要频繁移动元素时,使用
OrderedDict更合适。
9. 合并、解包与更新
9.1 使用 ** 解包合并
从 Python 3.5 开始,可以在字典字面量中使用 ** 解包多个字典:
d1 = {'a': 1}
d2 = {'b': 2}
merged = {**d1, **d2} # {'a': 1, 'b': 2}- 如果存在重复键,后面的字典会覆盖前面的。
- 该方法适用于任意映射类型(如
defaultdict),因为解包操作会调用keys()和__getitem__。
9.2 使用 | 和 |= 运算符(Python 3.9+)
d1 | d2:返回一个新字典,合并两个字典,后者覆盖前者。d1 = {'a': 1} d2 = {'a': 2, 'b': 3} result = d1 | d2 # {'a': 2, 'b': 3}d1 |= d2:就地更新d1,等价于d1.update(d2)。- 注意:
|运算符要求两侧都必须是dict实例,而**解包可以接受任意映射。
9.3 使用 update()
update() 方法可以接受多种参数类型:
- 映射对象:
d.update(other_dict) - 键值对的可迭代对象:
d.update([('a', 1), ('b', 2)]) - 关键字参数:
d.update(a=1, b=2) - 可混合使用,但关键字参数优先级最高。
9.4 合并时的覆盖规则
无论使用哪种方式,如果多个来源包含相同的键,最终值取决于最后出现的来源。例如:
merged = {**d1, **d2} # d2 覆盖 d1
d1.update(d2) # d2 覆盖 d1
d1 | d2 # d2 覆盖 d110. 高级模式与技巧
利用字典可以优雅地解决许多常见编程问题。
10.1 分组(Grouping)
将元素按照某个键分组,常用 setdefault 或 defaultdict:
data = [('a', 1), ('b', 2), ('a', 3)]
groups = {}
for key, value in data:
groups.setdefault(key, []).append(value)
# {'a': [1, 3], 'b': [2]}或使用 defaultdict(list) 简化。
10.2 反转字典
将键值互换:
original = {'a': 1, 'b': 2}
reversed_dict = {v: k for k, v in original.items()}注意:如果原字典的值有重复,反转后会丢失部分键。
10.3 多键映射
有时需要多个键对应同一个值,可以使用 frozenset 作为键,或者维护多个字典:
# 使用 frozenset 作为键
mapping = {frozenset(['a', 'b']): 1}或者使用辅助字典记录别名:
aliases = {'a': 'primary', 'b': 'primary', 'primary': 'primary'}
value = data[aliases[key]]10.4 分派表(Dispatch Table)
用字典替代冗长的 if-elif 链:
def add(x, y): return x + y
def subtract(x, y): return x - y
operations = {
'add': add,
'subtract': subtract,
}
result = operations[op](x, y)10.5 缓存 / 记忆化
使用字典保存函数结果,避免重复计算:
cache = {}
def fib(n):
if n in cache:
return cache[n]
if n < 2:
result = n
else:
result = fib(n-1) + fib(n-2)
cache[n] = result
return resultPython 标准库的 functools.lru_cache 就是基于字典实现的。
10.6 属性式访问
让字典的键可以像对象属性一样访问:
- 使用
types.SimpleNamespace:from types import SimpleNamespace d = {'name': 'Alice', 'age': 30} obj = SimpleNamespace(**d) print(obj.name) # Alice - 自定义类,继承
dict并实现__getattr__/__setattr__,或使用__getattribute__。
10.7 嵌套字典
处理多层级数据时,可以使用 defaultdict(dict) 自动创建子字典:
from collections import defaultdict
tree = defaultdict(dict)
tree['a']['b'] = 1手动方式:
tree = {}
tree.setdefault('a', {})['b'] = 111. collections 中的字典变体
collections 模块提供了多种字典的扩展类型,适用于不同场景。
11.1 OrderedDict
- 内部使用双向链表维护键值对的顺序,支持高效的重新排序操作。
- 额外方法:
move_to_end(key, last=True):将指定键移动到末尾(默认)或开头(last=False)。popitem(last=True):可指定弹出最后一项(默认)或第一项(last=False)。
- 与普通字典不同,
OrderedDict的相等比较顺序敏感。 - 适合需要频繁调整顺序或需要实现 LRU 缓存的场景(Python 3.2+ 的
functools.lru_cache内部使用OrderedDict)。
11.2 defaultdict
- 构造函数接受一个
default_factory可调用对象(如list、int、set),当访问不存在的键时,自动调用该工厂生成默认值并插入。 - 示例:
from collections import defaultdict dd = defaultdict(list) dd['a'].append(1) # 无需初始化,自动创建空列表 - 注意:访问不存在的键会产生副作用(创建条目),有时可能不是期望的行为。
- 实现依赖
__missing__钩子。
11.3 Counter
- 用于计数可哈希对象,是字典的子类,键为元素,值为计数。
- 缺失键返回 0(而不是抛出异常)。
- 常用方法:
most_common([n]):返回计数最高的 n 个元素及计数。elements():返回一个迭代器,每个元素重复其计数次。subtract([iterable-or-mapping]):从计数中减去。
- 支持算术和集合运算:
+、-、&、|(结果中计数为负或零的项会被丢弃)。 - 示例:
from collections import Counter c = Counter('abracadabra') c.most_common(2) # [('a', 5), ('b', 2)]
11.4 ChainMap
- 将多个映射链接起来,形成一个逻辑上的单一映射。
- 查找时按照传入顺序依次搜索各个映射,直到找到键。
- 修改操作(如
__setitem__、update)只影响第一个映射。 - 适合管理多层作用域或配置覆盖(如默认配置 + 用户配置)。
from collections import ChainMap defaults = {'color': 'red', 'size': 'M'} user = {'color': 'blue'} combined = ChainMap(user, defaults) combined['color'] # 'blue'(来自 user) combined['size'] # 'M'(来自 defaults)
11.5 UserDict
- 一个纯 Python 实现的字典包装类,内部数据存储在
self.data属性中。 - 设计目的是方便继承和扩展,避免直接继承内置
dict时的一些行为陷阱(如__setitem__不会被update调用)。 - 适合需要自定义字典行为的场景。
11.6 MappingProxyType(只读代理)
- 位于
types模块,创建一个只读的动态视图。 - 底层字典的任何变化都会反映到代理中,但无法通过代理修改字典。
from types import MappingProxyType d = {'a': 1} proxy = MappingProxyType(d) proxy['a'] # 1 # proxy['b'] = 2 # TypeError: 'mappingproxy' object does not support item assignment d['b'] = 2 # 修改原字典,代理可见
11.7 弱引用字典
weakref.WeakKeyDictionary:键为弱引用,当键对象不再被其他强引用引用时,该条目自动被删除。weakref.WeakValueDictionary:值为弱引用,当值对象被回收时,对应条目自动删除。- 常用于缓存或对象注册表,避免内存泄漏。
14. 序列化与转换
字典与外部数据格式之间的转换是常见需求。
14.1 JSON 转换
- 字典 → JSON 字符串:
json.dumps(d)import json d = {'name': 'Alice', 'age': 30} json_str = json.dumps(d) # '{"name": "Alice", "age": 30}' - JSON 字符串 → 字典:
json.loads(s)d = json.loads('{"name": "Alice", "age": 30}') - 注意:JSON 对象要求键必须是字符串,且值类型有限(字符串、数字、布尔、null、数组、对象)。Python 字典的非字符串键在序列化时会被转换为字符串(可能引起意外),非标准类型需要自定义编码器。
14.2 pickle 序列化
- 可以将任意 Python 对象(包括字典)序列化为字节流,并完整恢复。
import pickle d = {'a': [1, 2, 3], 'b': {'c': 4}} data = pickle.dumps(d) restored = pickle.loads(data) - 优点:支持几乎所有 Python 对象;缺点:不安全(反序列化不可信数据可能执行恶意代码),且格式非跨语言。
14.3 ast.literal_eval
- 安全地将字符串形式的 Python 字面量(包括字典)转换为实际对象。
import ast s = "{'a': 1, 'b': [2, 3]}" d = ast.literal_eval(s) - 比
eval安全,因为它只评估字面量表达式,不允许任意代码执行。
14.4 与其他结构的互转
- 字典 → 键值对列表:
list(d.items()) - 键值对列表 → 字典:
dict(list_of_pairs) - 两个列表 → 字典:
dict(zip(keys, values)) - 字典 → 键的集合:
set(d)或set(d.keys()) - 字典 → DataFrame(pandas):
import pandas as pd df = pd.DataFrame(d) # 如果 d 的值是列表或 Series # 或 df = pd.DataFrame.from_dict(d, orient='index') - DataFrame → 字典:
df.to_dict()支持多种方向('dict'、'list'、'records'等)。 - 命名元组/数据类与字典:可使用
dataclasses.asdict()或手动转换。
以上详细总结了指定部分的字典知识,涵盖了核心概念、创建方式、操作复杂度、常用方法、视图、有序性、合并技巧、高级模式、标准库变体以及序列化转换,内容具备深度和广度,适合系统学习和参考。