17370845950

Python 高性能字典与集合使用技巧
dict 和 set 在 CPython 中快是因为底层用高度优化的哈希表,平均时间复杂度 O(1),但性能受哈希函数、冲突、内存布局及构造方式等影响;dict.fromkeys() 批量初始化更快;成员检测优先用 set;__slots__ 可节省内存并加速属性访问。

为什么 dictset 在 CPython 中快得不像 Python?

因为底层用的是哈希表(hash table),且 CPython 实现高度优化:插入、查找、删除平均时间复杂度都是 O(1)。但这个“快”有前提——哈希函数要快,冲突要少,内存布局要友好。

常见误区是以为“只要用 dict 就一定快”,其实构造方式、键类型、扩容时机都会显著影响性能:

  • dict() 空初始化比 {} 略慢(字节码多一条 CALL_FUNCTION
  • 用可变对象(如 list)当 dict 键会直接报 TypeError: unhashable type
  • 一次性插入大量数据时,预设容量能避免多次 rehash——但 Python 不支持直接指定初始大小,只能靠“先建再更新”的 trick

如何让 dict.fromkeys() 成为批量初始化的首选?

当你需要一个键存在、值统一的字典(比如去重后标记为 True),dict.fromkeys(iterable, value) 比循环 dict[key] = value 快 2–3 倍,因为它绕过了 Python 层的键值对赋值逻辑,直接在 C 层批量构建哈希表。

注意两个坑:

  • 第二个参数是引用传递:如果传的是可变对象(如 []),所有键共享同一个实例 → 改一个全改
  • 它不检查键是否重复:输入含重复元素时,结果字典只保留最后一个出现的键

示例:

valid_keys = ["a", "b", "c"]
flags = dict.fromkeys(valid_keys, False) # ✅ 安全
cache = dict.fromkeys(valid_keys, {}) # ❌ 危险:所有键共用一个 dict

什么时候该用 set 而不是 list 做成员检测?

只要涉及 if x in container:,且容器长度 > 100,几乎总是该换 set。列表是 O(n) 线性扫描,集合是 O(1) 哈希查找——10 万元素下,前者可能耗时 10ms+,后者稳定在 0.1ms 内。

但别忽略代价:

  • 构建 set 本身要时间:从 listset 是 O(n),如果只查一次,不如直接遍历
  • set 占内存更大:每个元素额外存哈希值 + 更宽松的负载因子(默认 0.625),实际内存占用约是等效 list 的 2–3 倍
  • 顺序不保留(Python 3.7+ dict 有序,但 set 依然无序)

__slots__ 对自定义类里用 dict 存属性的影响

如果你写了一个高频实例化的类,又习

惯用 self.__dict__ 动态存属性(比如解析 JSON 后挂载字段),那它的 dict 其实是每个实例独占的一份哈希表。此时开启 __slots__ 能省下每实例 ~50 字节,并加快属性访问——因为绕过了 __dict__ 查找路径。

但要注意限制:

  • 一旦定义 __slots__,实例就不再有 __dict__,不能动态新增未声明的属性
  • 继承链中只要有一个父类用了 __slots__,子类也必须显式定义(哪怕为空 __slots__ = ()),否则报错
  • 如果类本身需要 __dict__(比如用 vars() 或某些 ORM),就不能用 __slots__

典型适用场景:DTO 类、解析后的消息体、高频创建的中间对象。