成为Python砖家(6): 字典的key有什么要求?

第一次理解: 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 对象,需要时请查阅文档进一步学习。

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值