目录
(1)以下代码只用到了__eq__,因为没涉及一中的说明,这里根本没涉及hash
(3)如果自定义类重写了__hash__()方法没有重写__eq__()方法
一、说明
注意魔法方法__hash__的使用场景有二:
(1)被内置函数hash()调用
(2)hash类型的集合对自身成员的hash操作:set(), frozenset([iterable]), dict(**kwarg)
以下是文档说明:
代码版本3.6.3 文档版本:3.6.6
object.__hash__(self)
Called by built-in function hash() and for operations on members of
hashed collections including set, frozenset, and dict.
二、应用
(1)以下代码只用到了__eq__,因为没涉及一中的说明,这里根本没涉及hash
(即使你把我注释掉的__hash__放开也不会有任何对__hash__的调用)
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# def __hash__(self):
# print(self.name, '使用了__hash__方法')
# return hash(self.name)
def __eq__(self, other):
print(self.name, '使用了__eq__方法')
return self.__dict__ == other.__dict__
person1 = Person('zs', 20)
person2 = Person('ls', 20)
person3 = Person('ww', 30)
person4 = Person('zs', 20)
print(person1 == person4)
print(person2 == person3)
(2)如果自定义类重写了__eq__()方法没有重写__hash__()方法,那么这个类无法作为哈希集合的元素使用(这个hashable collections指的是set、frozenset和dict)。
比如哈希集合放的全是对象,只定义的__eq__,没定义__hash__,会报错:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# def __hash__(self):
# print(self.name, '使用了__hash__方法')
# return hash(self.name)
def __eq__(self, other):
print(self.name, '使用了__eq__方法')
return self.__dict__ == other.__dict__
person1 = Person('zs', 20)
person2 = Person('ls', 20)
person3 = Person('ww', 30)
person4 = Person('zs', 20)
set1 = {person1, person2, person3, person4}
print(set1)
结果:
这其实是因为重写__eq__()方法后会默认把__hash__赋为None(文档后面有说),像list一样。如下面测试:
class A:
def __eq__(self, other):
pass
a = A()
print(a.__hash__) # None
hash(a)
# TypeError: unhashable type: 'A'
(3)如果自定义类重写了__hash__()方法没有重写__eq__()方法
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __hash__(self):
print(self.name, '使用了__hash__方法')
return hash(self.name)
# def __eq__(self, other):
# print(self.name, '使用了__eq__方法')
# return self.__dict__ == other.__dict__
person1 = Person('zs', 20)
person2 = Person('ls', 20)
person3 = Person('ww', 30)
person4 = Person('zs', 20)
set1 = {person1, person2, person3, person4}
print(set1)
不报错,但不符合需求———我们认为person1与person4是同一个人,他们却都被添加到集合,违背了集合的理念:
zs 使用了__hash__方法
ls 使用了__hash__方法
ww 使用了__hash__方法
zs 使用了__hash__方法
{<__main__.Person object at 0x0000000000719CF8>,
<__main__.Person object at 0x00000000007192E8>,
<__main__.Person object at 0x0000000000719CC0>,
<__main__.Person object at 0x0000000000719320>}
我们期望中没有person4.为什么这里会有person4呢?而且person1与person4的hash(name)是相同的,为什么最后还是加到了集合呢? 主要是因为当发现hash出的值相同时,就需要__eq__进行下一步判断。我们重写了__hash__却没重写__eq__,默认调用了object的__eq__,而默认的__eq__比较的是对象地址,person1与persperson4地址不同,所以将最后的person4加到了集合.
可以看到person1与person4的hash相同,以为我们重写的__hash__是根据名字,所以需要__eq__进一步判断:
(4)hash与eq都重写
下面代码是正确的:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __hash__(self):
print(self.name, '使用了__hash__方法')
return hash(self.name)
def __eq__(self, other):
print(self.name, '使用了__eq__方法')
return self.__dict__.get("name") == other.__dict__.get("name")
person1 = Person('zs', 20)
person2 = Person('ls', 20)
person3 = Person('ww', 30)
person4 = Person('zs', 20)
set1 = {person1, person2, person3, person4}
print(set1)
上面代码执行原理:因为要加入集合,所以每个person都会调用__hash__,我们这里重写了,所以用我们重写的__hash__。
我们重写的__hash__是比较每个对象内容中的name属性。当hash(self.name)值不同时,name肯定不同,所以就不会再调用
__eq__,直接将对象加入集合就好了;而当hash(name)值相同时,我们不能盲目认为不能加入集合,因为不同的name可能hash出一样的值,(比如:若hash算法是a%7 = b ,其中a 是要hash的参数,b是hash(a)后得到的地址值。当a = 7时 b=0,而当a =14时 , b 还是0,但是7和14明显不同。),这时候就会再调用我们重写的__eq__帮忙来判断所有内容是否相同了。