为何Python带可变默认参数的函数可哈希而含可变元素的元组不可?
元组和函数的哈希判定逻辑核心差异
1. 元组的哈希逻辑
元组是值语义的不可变容器,它的哈希计算和相等判定完全对齐:
- 元组的
__hash__方法会递归遍历所有元素,把所有元素的哈希值组合计算得到最终结果 - 如果元组内包含任何不可哈希的元素(比如列表),递归计算哈希时就会抛出错误,导致整个元组不可哈希
- 元组的相等判定是「所有元素的值都相等」,因此只要元组可以哈希,就保证值相同的元组哈希值相同,符合哈希的一致性要求
对应示例的报错原因就是元组内的列表不可哈希,递归计算失败:
In [1]: t = (1,2,[3]) In [2]: hash(t) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-2-8a01c6d8dba1> in <module> ----> 1 hash(t) TypeError: unhashable type: 'list'
2. 函数的哈希逻辑
函数是身份语义的对象,哈希计算和相等判定都只和对象身份绑定:
- 函数属于内置
function类的实例,该类的__hash__方法默认基于对象的内存ID计算,完全不依赖任何内部属性(包括存储默认参数的__defaults__属性、函数代码本身以外的其他属性) - 函数的相等判定默认也是「是否是同一个内存地址的对象」,和内部属性的变化无关
- 哪怕修改了函数的默认参数(本质是修改了
f.__defaults__属性的值),只要还是同一个函数对象,它的内存ID不会变,因此哈希值也不会变,完全符合哈希的一致性要求
对应示例中函数可哈希的原因就是哈希只依赖身份,和可变默认参数无关:
In [5]: def f(a: List = [1]): ...: a += a ...: return a ...: In [6]: hash(f) Out[6]: 8731603958411
两者设计差异的原因
这种差异是Python的设计取舍决定的:
- 元组的定位之一是作为不可变的复合键用于字典、集合等场景,因此需要按值判定相等和哈希
- 函数的定位是可调用对象,绝大多数场景下不需要按内部属性的不同区分不同的函数对象,默认按身份判定哈希和相等的成本更低、更符合使用习惯
内容的提问来源于stack exchange,提问作者Amir reza Riahi
相关产品推荐
相关产品推荐

