Python 散列
最后修改于 2024 年 1 月 29 日
Python 散列教程解释了 Python 中的散列概念。我们解释了散列表和 Python 可散列对象。
散列表
散列表用于实现许多常见编程语言(如 C++、Java 和 Python)中的映射和集合数据结构。Python 对字典和集合使用散列表。散列表是键值对的无序集合,其中每个键都是唯一的。散列表提供了高效查找、插入和删除操作的组合。它们是数组和链表的最佳特性。
散列
散列是使用算法将任意大小的数据映射到固定长度的过程。这称为哈希值。散列用于创建高性能、直接访问的数据结构,其中需要存储大量数据并快速访问。哈希值是通过散列函数计算的。
Python 可散列
如果一个对象具有在其生命周期内永不改变的哈希值,则该对象是可散列的。(它可以在 Python 程序多次调用期间具有不同的值。)可散列对象需要 `__hash__` 方法。为了执行比较,可散列对象需要 `__eq__` 方法。
可散列性使对象可用作字典键和集合成员,因为这些数据结构在内部使用哈希值。Python 的不可变内置对象是可散列的;可变容器(如列表或字典)则不是。用户定义类的实例对象默认是可散列的。它们都比较不相等(除了与自身相比),并且它们的哈希值来自它们的 `id`。
Python 散列函数
`hash` 函数返回对象(如果它有的话)的哈希值。哈希值是整数。它们用于在字典查找期间快速比较字典键。对象可以实现 `__hash__` 方法。
Python 不可变内置类型是可散列的
Python 的不可变内置类型,如整数、字符串或元组,是可散列的。
#!/usr/bin/python
val = 100
print(val.__hash__())
print("falcon".__hash__())
print((1,).__hash__())
该示例打印了三个可散列对象的哈希值:一个整数、一个字符串和一个元组。
Python 自定义可散列示例 I
Python 自定义对象默认是可散列的。它们的哈希值来自它们的 ID。
#!/usr/bin/python
class User:
def __init__(self, name, occupation):
self.name = name
self.occupation = occupation
u1 = User('John Doe', 'gardener')
u2 = User('John Doe', 'gardener')
print('hash of user 1')
print(hash(u1))
print('hash of user 2')
print(hash(u2))
if (u1 == u2):
print('same user')
else:
print('different users')
在此示例中,我们有一个 `User` 的两个实例。
u1 = User('John Doe', 'gardener')
u2 = User('John Doe', 'gardener')
我们有两个具有相同数据的实例。
print('hash of user 1')
print(hash(u1))
`hash` 函数返回对象的哈希值。默认实现来自对象的 ID。
$ python custom_object.py hash of user 1 -9223371894419573195 hash of user 2 142435202673 different users
尽管用户信息相同,但比较结果显示是不同的对象。要更改这一点,我们需要实现 `__eq__` 方法。
Python 自定义可散列示例 II
在第二个示例中,我们实现了一个自定义的 `__eq__` 方法。
#!/usr/bin/python
class User:
def __init__(self, name, occupation):
self.name = name
self.occupation = occupation
def __eq__(self, other):
return self.name == other.name \
and self.occupation == other.occupation
def __str__(self):
return f'{self.name} {self.occupation}'
u1 = User('John Doe', 'gardener')
u2 = User('John Doe', 'gardener')
if (u1 == u2):
print('same user')
print(f'{u1} == {u2}')
else:
print('different users')
# users = {u1, u2}
# print(len(users))
现在,比较返回了我们期望的输出;但是,我们无法将对象插入 Python 集合中;这将导致 `TypeError: unhashable type: 'User'`。要更改这一点,我们需要实现 `__hash__` 方法。
Python 自定义可散列示例 III
在第三个示例中,我们实现了 `__eq__` 和 `__hash__` 方法。
#!/usr/bin/python
class User:
def __init__(self, name, occupation):
self.name = name
self.occupation = occupation
def __eq__(self, other):
return self.name == other.name \
and self.occupation == other.occupation
def __hash__(self):
return hash((self.name, self.occupation))
def __str__(self):
return f'{self.name} {self.occupation}'
u1 = User('John Doe', 'gardener')
u2 = User('John Doe', 'gardener')
users = {u1, u2}
print(len(users))
if (u1 == u2):
print('same user')
print(f'{u1} == {u2}')
else:
print('different users')
print('------------------------------------')
u1.occupation = 'programmer'
users = {u1, u2}
print(len(users))
if (u1 == u2):
print('same user')
print(f'{u1} == {u2}')
else:
print('different users')
该示例比较了两个具有自定义 `__eq__` 和 `__hash__` 方法实现的对象的哈希值。这些对象可以插入 Python 集合中,并且在稍后更改属性时,我们会得到预期的输出。
def __hash__(self):
return hash((self.name, self.occupation))
`__hash__` 函数的实现返回一个通过 `hash` 函数从属性元组计算出的哈希值。
$ python custom_object3.py 1 same user John Doe gardener == John Doe gardener ------------------------------------ 2 different users
Python @dataclass 装饰器
自 Python 3.7 起,我们有了 `dataclass` 装饰器,它会自动生成一些样板代码。
dataclass 装饰器有一个 `frozen` 参数(默认为 `False`)。如果指定,字段将是冻结的(即只读)。如果 `eq` 设置为 `True`(默认如此),则会实现 `__hash__` 方法,并且对象实例将是可散列的。
#!/usr/bin/python
from dataclasses import dataclass
@dataclass(frozen=True)
class User:
name: str
occupation: str
u1 = User('John Doe', 'gardener')
u2 = User('John Doe', 'gardener')
if (u1 == u2):
print('same user')
print(f'{u1} == {u2}')
else:
print('different users')
users = {u1, u2}
print(len(users))
该示例使用了 `@dataclass` 装饰器。
$ python decorator.py same user User(name='John Doe', occupation='gardener') == User(name='John Doe', occupation='gardener') 1
来源
在本文中,我们涵盖了 Python 中的散列。
作者
列出所有 Python 教程。