You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:34:31