第一次理解: dictionary 的定义
是一个关联数组。其中的每个键,都映射到对应的值。键可以是任何具有 __hash__()
和 __eq__()
方法的对象。在 Perl 中称为 hash。
上述解释来自 Python 文档 file:///Users/zz/Documents/pydoc-zh-cn/python-3.12.5-docs-html/glossary.html#term-dictionary。它不是很清晰,没有提到 hashable(可哈希性)。
第二次理解:hashable 可哈希性
文档: file:///Users/zz/Documents/python-3.12.5-docs-html/glossary.html#term-hashable
hashable是用来描述某个对象的。当某个对象具有如下两个特点,它就是hashable的:
● 在生命期间绝不改变哈希值:这要求实现了 __hash__()
方法
● 可以与其他对象进行比较:这要求实现了 __eq__()
方法
hashable 有什么用处?有两个典型用处:
● 用作 dict 的 key
● 用作 set 的元素
原因是,这两种数据结构要在内部使用哈希值。
第三次理解:__hash__()
方法初探
file:///Users/zz/Documents/pydoc-zh-cn/python-3.12.5-docs-html/reference/datamodel.html#object.__hash__
当一个对象具备了 __hash__()
方法, 它就能够被 Python 内置的 hash()函数调用了。举例:
>>> a = "hello"
>>> hash(a)
2642687002406759454
__hash__()
方法需要返回一个整数, 因此可以这样子实现:
def __hash__(self):
return hash((self.name, self.nick, self.color))
第四次理解: __eq__()
方法初探
file:///Users/zz/Documents/python-3.12.5-docs-html/reference/datamodel.html#object.__eq__
当使用 x == y做两个对象 x 和 y 相等的判断时, 执行的是 x.__eq__(y)
.
第五次理解: 可以不定义 __hash__()
和 __eq__()
吗?
可以不定义。
当定义一个 class, 这个 class 既不定义 __hash__()
也不定义 __eq__()
,Python 会使用默认的实现。此时这个类的实例,可以作为 dict 的key:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
obj1 = Point(0, 0)
my_dict = {obj1: "origin"}
当定义一个 class, 这个class内部定义了__eq__()
方法,此时如果不定义 __hash__
方法,那么 Python 会自动将 __hash__()
设置为 None。这有什么问题呢?当它作为 dict 的key时,会抛出异常:TypeError: unhashable type:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return isinstance(other, Point) and self.x == other.x and self.y == other.y
obj1 = Point(0, 0)
my_dict = {obj1: "origin"}
Traceback (most recent call last):
File "/Users/zz/work/toys/a9/a.py", line 50, in <module>
my_dict = {obj1: "origin"}
^^^^^^^^^^^^^^^^
TypeError: unhashable type: 'Point'
当定义一个 class, 定义__hash__()
方法,但不定义 __eq__()
方法. 这个类的实例作为 dict 的key 时,不会报错:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return hash((self.x, self.y))
obj1 = Point(0, 0)
my_dict = {obj1: "origin"}
obj2 = Point(0, 0)
print(obj1 == obj2) # False
只不过这可能会得到一个很粗糙的比较,对于复杂的类,建议__hash__()
和 __eq__()
配套定义。 例如仍然是刚刚定义的简单class Point, 之前的 obj1 和 obj2 的相等比较,返回的是 false; 而当定义了 __eq__()
为同时判断实例类型、x和y元素取值, 并且同时定义 __hash__()
函数,就可以在判断 ==时,得到 True 的结果:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return hash((self.x, self.y))
def __eq__(self, other):
return isinstance(other, Point) and self.x == other.x and self.y == other.y
obj1 = Point(0, 0)
my_dict = {obj1: "origin"}
obj2 = Point(0, 0)
print(obj1 == obj2) # True
总结
本文从 Python 文档对于 dictionary (字典)的定义出发,简单的探索了 hashable 的概念,并给出了 __hash__()
和 __eq__()
两个方法的简单说明。然后以 class Point类作为依托,展示了当只定义 __eq__()
而不定义 __hash__()
会导致作为 dict 的 key 时会触发 TypeError, 全都定义、全不定义、只定义 __hash__()
而不定义 __eq__()
时,则都是可以作为字典的 key 的。
本文并没有深入探讨 __hash__()
和 __eq__()
, 也没提及 mutable 和 immutable 对象,需要时请查阅文档进一步学习。