Python中自定义可变对象为何可哈希并能作为字典键使用?
为什么自定义可变类的实例能作为字典的键?
嘿,这个问题戳中了Python哈希机制里一个很容易混淆的点——很多人会把“对象可变”和“不可哈希”直接划等号,但其实这俩不是完全绑定的!
核心逻辑:可哈希性的关键是「哈希值的稳定性」
Python判断一个对象能不能当字典(dict)的键,本质看两个条件:
- 这个对象实现了
__hash__()方法,且在整个生命周期内,哈希值不会改变 - 同时满足「如果
a == b,那么hash(a) == hash(b)」的一致性规则
内置可变类型(list/dict/set)为什么不能当键?
像list、dict这种内置可变类型,它们的哈希值是基于自身内容计算的——一旦你修改了它们的元素(比如给list加个元素,给dict改个值),哈希值就会立刻变化。如果允许它们当键,字典就会找不到之前存储的对应值(因为键的哈希变了,映射的位置也变了)。所以Python干脆给这些类型禁用了__hash__()方法(或者说把__hash__设为None),直接阻止它们成为字典键。
你的自定义类实例为啥可以?
你定义的RandomObject类,在没重写__eq__()和__hash__()的情况下,Python会给它默认的实现:
- 默认的
__hash__()是基于对象的内存地址生成哈希值的——只要这个实例没被销毁,它的内存地址就不会变,哈希值也就一直稳定 - 默认的
__eq__()方法是比较两个对象的内存地址,只有同一个实例才会返回True
这就完全满足了可哈希的两个条件!哪怕你修改实例的属性(比如my_obj.x = 30),实例的内存地址没变,哈希值也就没变,字典依然能通过这个实例找到对应的值。
举个实际的代码验证:
class RandomObject: def __init__(self, x): self.x = x # 创建实例并作为键存入字典 my_obj = RandomObject(25) my_dict = {my_obj: "hello"} # 修改实例的属性 my_obj.x = 100 # 依然能通过实例查到值 print(my_dict[my_obj]) # 输出: hello # 另一个x相同的实例,因为内存地址不同,哈希也不同 another_obj = RandomObject(25) print(my_dict.get(another_obj)) # 输出: None
什么时候自定义实例会失去可哈希性?
如果你重写了__eq__()方法但没重写__hash__(),Python会自动把这个类的__hash__()设为None,此时实例就不能当字典键了。比如:
class BadObject: def __init__(self, x): self.x = x # 重写__eq__,让x相等就判定对象相等 def __eq__(self, other): return self.x == other.x obj1 = BadObject(25) obj2 = BadObject(25) print(obj1 == obj2) # 输出: True # 此时obj1不能当字典键,会报错TypeError: unhashable type: 'BadObject' # my_dict = {obj1: "value"} # 执行这行会报错
这是因为默认的哈希还是基于内存地址,会出现obj1 == obj2但hash(obj1) != hash(obj2)的情况,违反了哈希的一致性规则,所以Python直接禁用了它的可哈希性。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

