浅析 Python 字典的键
Python 字典的键的定义
什么类型可以作为字典的键,在 Python 字典介绍 中介绍到 Python 的 Dict 本质是一个 Mapping 对象,它本质是将一个 hashable 值映射到一个任意对象上。这里的 hashable 值就是字典的键,任意对象就是字典的值。
Python 中的 hashable 值
什么是 hashable 值呢?在 Hashable - 可哈希 中定义了 hashable ,其中主要有以下几个特点:
- 在一个对象的生命周期内,它的 hash 值不变 (
__hash__()
方法的返回值不变) - 如果两个对象是相等的,那么它们的 hash 值也必须相等
- 大多数的内置的不可变类型都是可 hash 的,如,
int
、float
、str
- 可变容器都不可 hash ,如,
list
、dict
、set
- 不可变容器只有当所有的元素都可 hash 时,才可 hash ,如,
tuple
、frozenset
- 用户自定义的类,默认都是可 hash 的, hash 值的生产基于对象的
id()
代码示例
1. 基础不可变类型
1 | dict_a = { |
2. 可变的容器类型
1 | dict_a = { |
3. 不可变的容器类型
1 | dict_a = { |
4. 自定义的类
默认自定义的类都是可 hash 的
1
2
3
4
5
6
7
8
9
10class A:
pass
dict_a = {
A: "any_type",
A(): "any_type",
A(): "any_type",
}
print(len(dict_a)) # 3定义了
__hash__()
方法的类,其实例也是可 hash 的1
2
3
4
5
6
7
8
9
10
11class A:
def __hash__(self):
return id(self)
dict_a = {
A: "any_type",
A(): "any_type",
A(): "any_type",
}
print(len(dict_a)) # 3仅当
__hash__()
相等时,发生 hash 冲突,但不会覆盖值,因为默认对象的实例之间是不相等的,原因参考 值比较1
2
3
4
5
6
7
8
9
10
11class A:
def __hash__(self):
return 1
dict_a = {
A: "any_type",
A(): "any_type",
A(): "any_type",
}
print(len(dict_a)) # 3当仅定义了
__eq__()
方法时,其实例是不可 hash 的,解释参考__hash__()
1
2
3
4
5
6
7
8class A:
def __eq__(self, other):
return True
dict_a = {
A: "any_type",
A(): "any_type", # TypeError: unhashable type: 'A'
}当同时定义了
__hash__()
和__eq__()
方法时,其实例是可 hash 的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A:
def __hash__(self):
return id(self)
def __eq__(self, other):
return True
dict_a = {
A: "any_type",
A(): "any_type",
A(): "any_type",
}
print(len(dict_a)) # 3当两个实例的
__hash__()
的结果与__eq__()
均相等时,认为两个是同一个对象。会对值进行覆盖1
2
3
4
5
6
7
8
9
10
11
12
13
14class A:
def __hash__(self):
return 1
def __eq__(self, other):
return True
dict_a = {
A: "any_type",
A(): "any_type",
A(): "any_type",
}
print(len(dict_a)) # 2