Python中可变类实例可作为字典键的原因解析
Python类实例作为字典键的原理:可变对象为何能正常工作?
核心误解:字典键的要求不是“不可变”
字典对键的核心要求是可哈希(hashable),而非“不可变”。可哈希的定义是:
- 对象拥有一个在生命周期内永不改变的哈希值(通过
__hash__()方法获取) - 可以通过
__eq__()方法与其他对象判断相等性
“不可变”只是字符串、元组这类常见可哈希对象的特征,但不是成为字典键的必要条件——只要对象满足上述两个要求,哪怕内部状态可变,也能作为字典键。
默认类实例的哈希与相等性逻辑
Python自定义类的实例,默认行为遵循以下规则:
__hash__()方法返回对象的id()值(唯一标识内存地址的整数)__eq__()方法仅比较对象身份(即判断a is b是否为True)
这意味着:
- 只要实例未被销毁,它的哈希值永远不会改变,无论你修改多少属性
- 只有当两个实例是同一个内存地址的对象时,才会被判定为相等
你的代码为何能正常运行?
看你给出的示例代码:
class C: def __init__(self): self.val = 15 self.array = [] c = C() D = {c: 15} c.val = 14 c.array.append(15) print(D[c]) # 输出15
你修改的是实例c的属性,但c本身的内存地址(身份)没有变化,哈希值也保持不变。当执行D[c]时,字典会计算c的哈希值找到对应存储桶,再通过__eq__比较桶内键与c的身份——因为是同一个对象,匹配成功,所以能返回对应值。
注意:自定义__eq__会破坏默认行为
如果给类自定义了__eq__方法(比如基于属性值判断相等),但未同步自定义__hash__方法,Python会自动将类的__hash__设为None,此时实例会变成不可哈希类型,无法作为字典键,执行时会抛出TypeError: unhashable type异常。
示例:
class C: def __init__(self): self.val = 15 def __eq__(self, other): return self.val == other.val c = C() D = {c: 15} # 报错:TypeError: unhashable type: 'C'
这是因为自定义__eq__后,默认基于身份的哈希逻辑不再合理——若两个不同实例的属性值相等,它们应该有相同的哈希值,但默认__hash__无法满足,因此Python会禁用哈希,强制你实现与__eq__逻辑匹配的__hash__方法。
总结
- 字典键的核心要求是可哈希,而非“不可变”
- 默认类实例的哈希与相等性基于对象身份,因此即使属性可变,只要对象本身存在,就能正常作为字典键
- 自定义
__eq__时必须同步自定义__hash__,否则实例会变为不可哈希类型
内容的提问来源于stack exchange,提问作者flameshooter09
相关产品推荐
相关产品推荐

