ZetCode

Python __hash__ 方法

最后修改于 2025 年 4 月 8 日

这篇综合指南探讨了 Python 的 __hash__ 方法,该特殊方法使对象可哈希。我们将介绍基本用法、不可变性要求、字典键、自定义哈希和实际示例。

基本定义

__hash__ 方法返回对象的整数哈希值。此哈希值用于字典键和集合中,以进行快速查找操作。

主要特征:对于相等的对象必须返回相同的哈希值,应该很快,并且应该均匀地分配值。具有 __hash__ 的对象称为可哈希对象,可以作为字典键。

基本 __hash__ 实现

这是一个简单的实现,展示了 __hash__ 如何与不可变对象一起工作。哈希基于对象的属性。

basic_hash.py
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 (self.x, self.y) == (other.x, other.y)

p1 = Point(1, 2)
p2 = Point(1, 2)
print(hash(p1) == hash(p2))  # True

此示例显示了一个可哈希的 Point 类。哈希基于坐标的元组。相等的点产生相同的哈希值。

请注意,我们还实现了 __eq__。这是保持哈希不变性的必要条件:相等的对象必须具有相等的哈希值。

使对象可哈希

要使对象可哈希,它们必须是不可变的。此示例演示如何创建一个不可变的可哈希类。

immutable_hash.py
class ImmutablePerson:
    __slots__ = ('_name', '_age')
    
    def __init__(self, name, age):
        self._name = name
        self._age = age
    
    @property
    def name(self):
        return self._name
    
    @property
    def age(self):
        return self._age
    
    def __hash__(self):
        return hash((self._name, self._age))
    
    def __eq__(self, other):
        return (self._name, self._age) == (other._name, other._age)

person = ImmutablePerson("Alice", 30)
d = {person: "value"}
print(d[person])  # "value"

这个不可变的 person 类使用属性和 __slots__ 来防止创建后属性的修改。哈希基于姓名和年龄。

该类可以用作字典键,因为它正确地实现了 __hash____eq__ 方法。

自定义哈希逻辑

有时您需要自定义哈希逻辑。此示例演示如何实现不区分大小写的字符串哈希。

custom_hash.py
class CaseInsensitiveString:
    def __init__(self, s):
        self._s = s.lower()
    
    def __hash__(self):
        return hash(self._s)
    
    def __eq__(self, other):
        return self._s == other._s
    
    def __repr__(self):
        return f"CaseInsensitiveString('{self._s}')"

s1 = CaseInsensitiveString("Hello")
s2 = CaseInsensitiveString("HELLO")
print(hash(s1) == hash(s2))  # True
d = {s1: "value"}
print(d[s2])  # "value"

此类在哈希之前将字符串规范化为小写,从而使哈希不区分大小写。相等的字符串(不区分大小写)产生相同的哈希值。

__eq__ 方法还比较规范化的字符串以保持哈希不变性。

可哈希容器

此示例演示了如何通过哈希其不可变内容来使自定义容器可哈希。

hashable_container.py
class HashableList:
    def __init__(self, items):
        self._items = tuple(items)
    
    def __hash__(self):
        return hash(self._items)
    
    def __eq__(self, other):
        return self._items == other._items
    
    def __getitem__(self, index):
        return self._items[index]
    
    def __len__(self):
        return len(self._items)
    
    def __repr__(self):
        return f"HashableList({list(self._items)})"

hl1 = HashableList([1, 2, 3])
hl2 = HashableList([1, 2, 3])
d = {hl1: "value"}
print(d[hl2])  # "value"

HashableList 在内部将项目存储在一个元组中,以确保不可变性。哈希基于此元组,使容器可哈希。

当您需要使用列表(或其他可变容器)作为字典键,同时保持不可变性时,此模式非常有用。

何时不实现 __hash__

此示例显示了一个可变类,由于其状态可以更改,因此不应实现 __hash__

mutable_unhashable.py
class MutablePoint:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def move(self, dx, dy):
        self.x += dx
        self.y += dy
    
    # No __hash__ implementation
    
    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

p = MutablePoint(1, 2)
# hash(p) would raise TypeError

这个可变 point 类没有实现 __hash__,因为它的坐标可以改变。Python 默认会使其不可哈希。

如果我们尝试在此处实现 __hash__,当点移动时,它会违反哈希不变性,从而可能导致字典查找问题。

最佳实践

资料来源

作者

我的名字是 Jan Bodnar,我是一位热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出所有 Python 教程