Python中hash()最常用三处:①支撑dict/set快速查找;②作@lru_cache缓存键;③自定义类通过__hash__实现哈希以用作dict键或set元素。

哈希函数在 Python 中最常用的三个地方
Python 里的 hash() 函数不是用来加密或校验文件的,它核心服务的是内置数据结构——比如 dict 和 set 的快速查找。当你往字典里存 {'a': 1},Python 实际上先对键 'a' 调用 hash('a'),算出一个整数,再根据这个整数决定把值存在哪块内存位置。
另外两个典型场景是:用作缓存键(比如 @lru_cache 默认拿参数的 hash() 值做键),以及自定义类时控制是否能放进 set 或作为 dict 的键——只要实现了 __hash__ 且不和 __eq__ 冲突,就能参与哈希运算。
为什么字符串、数字能哈希,而列表、字典不能
根本原因是可变性:list、dict、set 这些类型允许在创建后修改内容。一旦对象被放进 dict,它的哈希值就得稳定不变;否则下次查找时算出的哈希位置和当初插入时不一致,就找不到了。
所以 Python 直接禁止对可变对象调用 hash():
立即学习“Python免费学习笔记(深入)”;
hash([1, 2, 3]) # TypeError: unhashable type: 'list'
如果你真需要对类似结构做哈希(比如想缓存某次计算结果,输入是个列表),得先转成不可变形式:
-
hash(tuple(my_list))—— 适合一维数值/字符串列表 -
hash(str(my_dict))—— 不推荐,因为字典顺序不确定,Python 3.7+ 虽然保持插入序,但str({})输出格式可能随版本微调 -
hash(json.dumps(my_dict, sort_keys=True))—— 更稳妥,但注意浮点精度和 NaN 处理
hash() 的随机化与跨进程不一致问题
从 Python 3.3 开始,默认启用哈希随机化(Py_HASH_SEED),每次解释器启动时生成不同种子。这意味着:
- 同一段代码,在两次运行中,
hash("hello")可能返回不同整数 - 多进程环境下,子进程的
hash()结果和父进程不一致(除非显式设置环境变量PYTHONHASHSEED=0) -
dict和set的遍历顺序也不再稳定(这是副作用,不是 bug)
这设计是为了防止拒绝服务攻击(Hash DoS),但也会让依赖固定哈希值的逻辑出错——比如用 hash() 做持久化缓存键,或用于单元测试中硬编码哈希值断言。
别拿 hash() 当加密或唯一标识用
hash() 是为性能优化设计的,不是密码学安全哈希。它速度快、碰撞概率在实际使用中够低,但不抗碰撞性分析,也完全没考虑恶意构造输入。
如果你需要:
- 文件完整性校验 → 用
hashlib.sha256() - 用户密码存储 → 用
passlib或bcrypt,绝不用hash() - 生成唯一 ID → 用
uuid.uuid4(),而不是hash(time.time())
还有个容易忽略的点:hash() 返回的是有符号 C long(通常是 64 位),在 32 位系统或某些嵌入式 Python 实现里范围更小,直接拿来当数据库主键或网络传输 ID 很容易溢出或截断。










