今天刷一道算法题的时候用到了list_a == list_b
的判断,==
和is
大家都已经是耳熟能详了,前者是判断值是否相等,后者是判断引用是否相等,在用==
操作符进行判断的时候,其实内部调用的是__eq__
方法。比如
class Item:
def __init__(self, val):
self.val = val
def __eq__(self, other):
return self.val == other.val
first = Item('hello')
second = Item('hello)
print(first == second) # True
如果不实现__eq__
方法,那么自定义类型会调用默认的__eq__
方法, 通过默认方法进行比较的相等条件相当严格,只有自己和自己比才会返回True
,表现如下
class Item:
def __init__(self, val):
self.val = val
first = Item('hello')
second = Item('hello)
print(first == second) # False
因此,在需要进行自定义类型比较的时候,建议实现__eq__
方法。
谈及__eq__
方法,就不得不谈__hash__
,两者总是一起出现.在Python中,如果自定义类定义了__eq__
而未定义__hash__
方法的话,那么默认将__hash__
方法设置为None
。这会有什么潜在问题呢?
Python中的对象分为可变和不可变对象,我们从另一个角度来看,可以分为可哈希对象和不可哈希对象。通俗的说,可哈希对象可以作为字典的键,不可哈希对象无法作为字典的键。有时候,我们使用列表或者自定义对象作为字典的键,或者使用set
进行元素去重的时候,会遇到unhashable type: xxx
之类的问题,这类问题出现的原因就是字典的键或者集合中的元素类型为不可哈希类型。
那么常见的不可哈希类型有哪些呢?几乎都是一些常见的可变类型,比如列表、集合和字典等,都是不可哈希类型。有时候我们有对元素类型为不可变类型的对象进行去重或者使用它作为字典的key的需求,这个时候又该怎么做呢?
我们可以自定义一个类,来实现__eq__
和__hash__
方法达到这个效果,且看下面代码
class It(list):
def __init__(self, vals):
self.vals = vals
def __eq__(self, other):
return self.vals == other.vals
def __hash__(self):
# 注意__hash__需要返回一个整数
return hash(';'.join(vals))
s = set()
j = It(['a', 'b'])
s.add(j)
print(j in set) # True
k = It(['a', 'b'])
print(k in set) # True
从上面结果我们可以看到k
这个实例并未加入s
这个集合,但是在判断时返回了True
,原因就是j
和k
两者的__hash__
和__eq__
运算结果相同
我们改改代码,再看看结果
class It(list):
def __init__(self, vals):
self.vals = vals
def __eq__(self, other):
# 这里如果用 self == other就会出现无限递归,读者可以思考为什么
return id(self) == id(other)
def __hash__(self):
# 注意__hash__需要返回一个整数
return hash(';'.join(vals))
s = set()
j = It(['a', 'b'])
s.add(j)
print(j in set) # True
k = It(['a', 'b'])
print(k in set) # False
可以看到在关于通过hash
运算判断两个对象是否映射成一个值是需要__hash__
和__eq__
方法共同决定的