Python自定义对象哈希机制:__eq__与__hash__方法作用解析
嘿,这个困惑其实挺普遍的,我来给你把逻辑捋得明明白白~
先搞懂默认行为:没重写任何方法时
首先纠正一个误区:用户自定义类的实例默认是可以被哈希的,并不是不能。Python给所有自定义类都提供了默认的__eq__和__hash__实现:
- 默认的
__eq__:只比较对象的身份(也就是内存地址,对应id()函数的返回值),只有当两个对象是同一个实例(a is b为True)时,a == b才会返回True。 - 默认的
__hash__:返回的是对象id的哈希值(在CPython里直接返回id的值),所以每个实例的哈希值都是唯一的——毕竟每个对象的内存地址都不一样。
这时候你完全可以把自定义类的实例放进set里,或者作为dict的key,完全没问题。
重写__eq__会触发什么变化?
这就是你看到不同资料矛盾点的核心:如果你只重写了__eq__而没有重写__hash__,Python会自动把这个类的__hash__属性设为None,这时候实例就无法被哈希了,调用hash()会抛出TypeError。
为啥要这么做?因为哈希有个核心规则必须遵守:
如果
a == b为True,那么hash(a)必须等于hash(b)
如果你重写了__eq__(比如让两个不同内存地址的对象因为属性相同而相等),但还用默认的__hash__(基于id),就会打破这个规则——两个相等的对象哈希值不一样,这会导致依赖哈希的数据结构(比如set、dict)彻底混乱:比如你把两个相等的实例放进set,它会当成两个不同元素,完全不符合预期。
举个反例:
class Person: def __init__(self, name): self.name = name def __eq__(self, other): return isinstance(other, Person) and self.name == other.name p1 = Person("Alice") p2 = Person("Alice") print(p1 == p2) # 输出True hash(p1) # 抛出TypeError: unhashable type: 'Person'
正确的姿势:__eq__与__hash__要配套使用
如果你的类是不可变对象(属性一旦初始化就不会被修改),并且你重写了__eq__,那必须同时重写__hash__,让哈希值基于那些用来判断相等的属性。比如:
class Person: def __init__(self, name, age): self.name = name self.age = age # 不可变属性,初始化后不修改 def __eq__(self, other): if not isinstance(other, Person): return False return self.name == other.name and self.age == other.age def __hash__(self): # 用不可变的元组打包属性,计算哈希值 return hash((self.name, self.age))
这样,两个属性相同的Person实例,既满足p1 == p2,也满足hash(p1) == hash(p2),放进set或dict里就正常工作了。
如果你的类是可变对象(属性可以被修改),那建议要么不重写__eq__,要么重写__eq__后主动把__hash__设为None(其实Python会自动帮你做)。因为可变对象的属性修改后,哈希值也会跟着变,这会导致它在set或dict里“失踪”——比如你把实例放进set后修改了属性,下次查找就找不到它了,还会占用内存。
总结一下两者的角色
__eq__:定义对象的相等性规则,决定两个对象什么时候被视为相等。__hash__:定义对象的哈希值计算规则,必须满足“相等对象哈希值相同”的约束。- 两者的绑定逻辑:当你重写
__eq__时,Python会强制要求你要么同时重写__hash__(针对不可变类),要么禁用哈希(针对可变类),以此保证哈希数据结构的正确性。
内容的提问来源于stack exchange,提问作者Goktug

