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] = valueO(1)若键已存在则更新,否则插入
删除元素del d[key]O(1)键不存在时抛出 KeyError
弹出元素d.pop(key)O(1)删除并返回值,可提供默认值
成员检测key in dO(1)检查键是否存在
获取长度len(d)O(1)字典内部维护元素个数
清空d.clear()O(1) 或 O(n)通常直接释放内部表,视为 O(1)
遍历for k in dO(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, a

8.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 覆盖 d1

10. 高级模式与技巧

利用字典可以优雅地解决许多常见编程问题。

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 result

Python 标准库的 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'] = 1

11. 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() 或手动转换。

以上详细总结了指定部分的字典知识,涵盖了核心概念、创建方式、操作复杂度、常用方法、视图、有序性、合并技巧、高级模式、标准库变体以及序列化转换,内容具备深度和广度,适合系统学习和参考。