Python __hash__() 方法

Python __hash__() 语法

__hash__() 是 Python 的一个特殊方法(又称 “魔法方法” 或 “双下划线方法”),它用于定义当对象被 hash() 函数调用时,如何计算并返回其哈希值。

__hash__() 的主要作用是返回一个整数哈希值,从而使自定义对象能够作为字典的键或集合的元素(即成为可哈希对象)。

语法:

class MyObject:
    def __hash__(self):
        # 返回一个整数哈希值
        return hash((self.attr1, self.attr2))    # 通常基于对象的关键属性

说明:

__hash__() 方法不接收任何参数,除了 self(实例本身)。此外,__hash__() 方法必须返回一个整数(int 类型)。

对于 __hash__() 方法,小伙伴们要清楚以下几点。

  • 如果一个类实现了 __hash__() 方法,那么它的实例就是 “可哈希对象(hashable)” 。可哈希对象可以作为 “字典的键” 或 “集合的元素” 。
  • 如果一个类没有实现 __eq__() 方法,那么默认情况下,它继承自 object 的 __hash__() 方法,该方法会返回对象的 id()(内存地址)的哈希值。
  • 如果一个类实现了 __eq__() 方法但没有实现 __hash__() 方法,那么它的实例默认是不可哈希的(unhashable)。

可哈希性原则

如果两个对象相等(即 obj1 == obj2 为 True),那么它们的哈希值必须相等(即 hash(obj1) == hash(obj2) 必须为 True)。这是哈希函数最重要的特性。

如果一个对象是可变的(其内容在创建后可以改变),它通常不应该是可哈希的。因为如果对象内容改变了,但其哈希值保持不变,将导致在哈希表中无法正确查找。

Python __hash__() 摘要

属于 Python 魔法方法
使用频率
官方文档 查看
相关方法 __eq__()

Python __hash__() 示例

接下来,我们通过几个简单的例子来讲解一下 Python __hash__() 方法是如何使用的。

示例 1:对内置可哈希类型使用 hash()

# 整数
print(f'hash(10): {hash(10)}')
print(f'hash(-5): {hash(-5)}')

# 字符串
print(f"hash('hello'): {hash('hello')}")
print(f"hash('lvye'): {hash('lvye')}")

# 元组(如果所有元素都可哈希)
print(f"hash((1, 2, 'a')): {hash((1, 2, 'a'))}")

# 冻结集合 (frozenset)
print(f"hash(frozenset({1, 2, 3})): {hash(frozenset({1, 2, 3}))}")

# 尝试对不可哈希类型使用 hash()
try:
    print(hash([1, 2, 3]))     # 列表是可变的,不可哈希
except TypeError as e:
    print(f'捕获到错误: {e}')

try:
    print(hash({'a': 1}))     # 字典是可变的,不可哈希
except TypeError as e:
    print(f'捕获到错误: {e}')

运行结果如下。

hash(10): 10
hash(-5): -5
hash('hello'): 8349020324282453467
hash('lvye'): -7366604637856805683
hash((1, 2, 'a')): 2331017941867542542
hash(frozenset((1, 2, 3))): -272375401224217160
捕获到错误: unhashable type: 'list'
捕获到错误: unhashable type: 'dict'

分析:

内置的不可变类型(如整数、字符串、元组、frozenset)是可哈希的,因此可以计算它们的哈希值。

而可变类型(如列表、字典、集合)默认是不可哈希的,因为它们的内容可能会改变,导致哈希值不一致。

示例 2:自定义可哈希对象(同时实现 __eq__() 和 __hash__())

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        # 定义相等性:当 x 和 y 相同时,点相等
        if not isinstance(other, Point):
            return NotImplemented
        return self.x == other.x and self.y == other.y

    def __hash__(self):
        # 定义哈希值:基于 x 和 y 的元组哈希
        # 推荐的做法是哈希一个元组
        return hash((self.x, self.y)) 

    def __repr__(self):
        return f'Point({self.x}, {self.y})'

# 创建 Point 实例
p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)

print(f'hash(p1): {hash(p1)}')
print(f'hash(p2): {hash(p2)}')
print(f'hash(p3): {hash(p3)}')
print(f'p1 == p2: {p1 == p2}')   # True,因为 __eq__() 判断相等

运行结果如下。

hash(p1): -3550055125485641917
hash(p2): -3550055125485641917
hash(p3): 1079245023883434373
p1 == p2: True

分析:

当自定义类被用作字典键或集合元素时,通常需要我们同时实现 __eq__() 和 __hash__() 这两个方法。

在这个例子中,__hash__() 使用了其属性 x 和 y 的元组哈希值,确保了当 x 和 y 相同时,哈希值也相同。这使得 Point 对象可以作为字典的键和集合的元素,并且基于其内容进行相等性判断。

示例 3:仅实现 __eq__() 而不实现 __hash__()

class MyObject:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        # 定义相等性,但不定义哈希
        if not isinstance(other, MyObject):
            return NotImplemented
        return self.value == other.value

    def __repr__(self):
        return f'MyObject({self.value})'

# 创建实例
objA = MyObject(10)
objB = MyObject(10)
print(objA == objB)

# 尝试获取哈希值
try:
    print(hash(objA))
except TypeError as e:
    print(e)

# 尝试作为字典键或集合元素
dict = {}
try:
    dict[objA] = '绿叶网'
except TypeError as e:
    print(e)

运行结果如下。

True
unhashable type: 'MyObject'
unhashable type: 'MyObject'

分析:

如果一个类实现了 __eq__() 但没有显式实现 __hash__(),那么它的实例将默认是不可哈希的。这是为了防止违反 “相等则哈希值相同” 的规则。

在这个例子中,尽管 MyObject 的实例可以通过 __eq__() 进行相等性比较,但由于没有实现 __hash__(),它被认为是不可哈希的。

Python 这样做是为了避免在可哈希容器中出现逻辑错误(比如两个相等的对象却映射到不同的哈希桶)。当你定义了自定义的相等性,就必须也定义自定义的哈希值。

示例 4:显式将对象标记为不可哈希

class MyObject:
    __hash__ = None    # 显式标记为不可哈希

    def __init__(self, id, data):
        self.id = id
        self.data = data

    def __eq__(self, other):
        if not isinstance(other, MyObject):
            return NotImplemented
        return self.id == other.id    # 仅基于 id 判断相等

    def __repr__(self):
        return f'MyObject(id={self.id}, data={self.data})'

u1 = MyObject(1, 'foo')
u2 = MyObject(1, 'bar')
print(u1 == u2)   # True,因为 __eq__() 仅比较 id

try:
    print(hash(u1))
except TypeError as e:
    print(e)

运行结果如下。

True
(报错)unhashable type: 'MyObject'

分析:

我们可以通过将 __hash__() 设置为 None,来显式地将一个类标记为不可哈希。这对于你希望对象可比较但不可哈希的场景很有用。

将 __hash__() 设置为 None ,这是明确告诉 Python 解释器,该类的实例不支持哈希操作。即使这个类定义了 __eq__(),它也不能作为字典键或集合元素,因为这样可以避免潜在的不一致性(比如 u1 == u2 为 True,但它们在哈希表中可能被视为不同)。

上一篇: __format__()

下一篇: __bool__()

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号