python dict扩容触发条件为负载因子>0.75,初始容量设为8(2的幂),扩容后容量翻倍;rehash需遍历所有旧桶包括deleted标记;删除用哨兵对象占位,避免探测链断裂。

Python 内置的 dict 本身就是自动扩容的哈希表,手动重实现时,核心不是“能不能扩容”,而是“在什么条件下触发扩容”和“如何安全迁移旧桶中的键值对”。直接复用 dict 是最稳妥的选择;若为学习或嵌入式约束必须手写,关键在于扩容时机判断和 rehash 过程中避免键丢失或重复。
扩容触发条件:负载因子 > 0.75 时才真正需要扩容
Python 的 dict 实际使用约 0.625 的负载因子阈值(CPython 3.12),但教学实现常取 0.75 作为平衡点。低于该值扩容浪费空间,高于则冲突概率陡增,查找退化为 O(n)。
- 负载因子 =
len(self.items)/self.capacity,不是桶数量,是实际存储的键值对数 - 初始
capacity建议设为 8(2 的幂),便于位运算取模:hash(key) & (self.capacity - 1) - 扩容后容量翻倍(
self.capacity *= 2),不是加固定值,否则无法保持 2 的幂特性,位运算会出错
rehash 过程必须遍历所有旧桶,不能只 copy 非空桶
扩容后桶数组变长,每个键的哈希映射位置大概率改变。如果只复制非空桶,会漏掉那些因冲突链而存放在“非首位置”的键值对。
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
- 旧桶数组中每个 slot 要完整遍历:包括
None、已删除标记(如DELETED)、以及有效键值对 - 遇到
DELETED标记不能跳过,它占位影响线性探测逻辑,但也不应参与新表插入 - 新表插入仍需走完整哈希 + 探测流程,不能直接按旧索引赋值——因为
hash(key) & (new_capacity - 1)和旧值不同
删除操作必须留占位符,否则导致后续查找失败
线性探测哈希表中,删除后若直接置 None,会截断探测链,让本该在其后的键“不可达”。Python dict 内部用 dummy 对象作删除标记。
- 定义一个全局唯一哨兵对象:
DELETED = object(),不能用None或False -
__delitem__中找到键后,只把对应 slot 设为DELETED,不改self.size -
__setitem__在插入时,遇到DELETED可复用该槽位,但需继续探测确认键不存在 - 只有当
size降得很低(如
Python 原生 dict 已足够健壮,手写仅用于理解边界 case
CPython 的 dict 使用开放寻址 + 伪随机探测(非纯线性),并做了大量优化:缓存哈希值、紧凑存储、动态调整探测步长。手写版本很难在性能和正确性上匹敌。
- 真正需要自定义行为时(如指定哈希函数、支持不可哈希键),应继承
collections.abc.MutableMapping,底层仍用dict存储 - 调试时最容易忽略的是:
__hash__返回负数会导致位运算出错,务必用hash(key) & 0x7FFFFFFF或abs(hash(key))归一化 - 测试必须覆盖:连续插入触发扩容、插入后删除再插入、键哈希冲突密集等 case,否则 rehash 逻辑极易静默失效
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










