当对两个点的实例进行值的比较时,比如p1=Point(1,1) p2=Point(1,2)
,判断p1==p2
时__eq__()
会被调用,用以判断两个实例是否相等。在上述代码中定义了只要x和y的坐标相同,两个点相等。需要注意,__eq__()
对is
不生效,==
是比较的值,而is
比较的是引用,也就是内存地址。举个例子,p1=Point(1,1) p2=Point(1,1)
,p1==p2
为True
,p1 is p2
为False
,只有p1 is p1
为True
。
在Python中对象分为可哈希对象和不可哈希对象,可哈希对象如字符串、数字、自定义的类、frozenset、元组,被称作不可变对象,不可哈希对象如字典、列表、集合,被称作可变对象。这里的不可变不是对象的值不可变,而是指对象创建后其hash值在其生命周期内不会改变。用函数hash()
取可哈希对象的hash值,只要是同一对象其hash值不会改变;而对不可哈希对象取hash值,例如对列表取hash值,会报错,返回TypeError: unhashable type: 'list'
。可哈希对象因其hash值不变可以用作字典的key,而不可哈希对象则不行。
当需要对类的一个实例取其hash值时,会调用__hash__()
。一般来说,会把实例的所有属性打包成元组,返回其hash值,从而实现自定义__hash__()
。在用set()
去重时就是对比hash值是否一样,如果两个对象hash值一样代表重复。
用户定义的类默认带有__eq__()
和 __hash__()
方法;使用它们与任何对象(自己除外)比较必定不相等,并且 x.__hash__()
会返回一个恰当的值以确保 x == y
同时意味着 x is y
且 hash(x) == hash(y)
。
如果一个类没有定义__eq__()
方法,那么也不应该定义 __hash__()
操作;如果它定义了__eq__()
但没有定义 __hash__()
,那么__hash__()
会被隐式地设为None
,这个类就变成了不可哈希对象。如果一个类定义了可变对象并实现了 __eq__()
方法,则不应该实现__hash__()
,因为可哈希集的实现要求键的哈希集是不可变的。例如,Point
类中添加一个属性li
是一个列表,由于列表不可哈希所以强行放入包含属性的元组中并返回其哈希值会报错。
如果使用默认的__hash__()
则不论如何改变一个实例的值其hash值都不变;反之,使用本文这种自定义的__hash__()
方法,实例的值改变后,hash值就会改变。因此,自定义__hash__()
方法的类的实例不应该作为字典的key(强行作为key不会报错,但是改变实例的属性值会导致找不到key对应的value),key的哈希值必须唯一不可变,key的hash值改变会导致找不到key对应的value。