python 何时单用__hash__或__eq__何时一起用

目录

 

一、说明

二、应用

(1)以下代码只用到了__eq__,因为没涉及一中的说明,这里根本没涉及hash

 

(2)如果自定义类重写了__eq__()方法没有重写__hash__()方法,那么这个类无法作为哈希集合的元素使用(这个hashable collections指的是set、frozenset和dict)。

 (3)如果自定义类重写了__hash__()方法没有重写__eq__()方法

 (4)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加到了集合.

 (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__帮忙来判断所有内容是否相同了。

猜你喜欢

转载自blog.csdn.net/sinat_38068807/article/details/86519944