浅析 Python 字典的键

Python 字典的键的定义

什么类型可以作为字典的键,在 Python 字典介绍 中介绍到 Python 的 Dict 本质是一个 Mapping 对象,它本质是将一个 hashable 值映射到一个任意对象上。这里的 hashable 值就是字典的键,任意对象就是字典的值。

Python 中的 hashable 值

什么是 hashable 值呢?在 Hashable - 可哈希 中定义了 hashable ,其中主要有以下几个特点:

  • 在一个对象的生命周期内,它的 hash 值不变 ( __hash__() 方法的返回值不变)
  • 如果两个对象是相等的,那么它们的 hash 值也必须相等
  • 大多数的内置的不可变类型都是可 hash 的,如,intfloatstr
  • 可变容器都不可 hash ,如,listdictset
  • 不可变容器只有当所有的元素都可 hash 时,才可 hash ,如,tuplefrozenset
  • 用户自定义的类,默认都是可 hash 的, hash 值的生产基于对象的 id()

代码示例

1. 基础不可变类型

1
2
3
4
5
dict_a = {
1: "any_type",
1.5: "any_type",
"a": "any_type",
}

2. 可变的容器类型

1
2
3
4
5
dict_a = {
["a", "b"]: "any_type", # TypeError: unhashable type: 'list'
{"a", "b"}: "any_type", # TypeError: unhashable type: 'set'
{"a": "b"}: "any_type", # TypeError: unhashable type: 'dict'
}

3. 不可变的容器类型

1
2
3
4
5
6
7
8
9
dict_a = {
("a", "b"): "any_type",
frozenset(["a", "b"]): "any_type",
frozenset({"a": "b"}): "any_type",
("a", ("b", "c")): "any_type",
("a", ["b"]): "any_type", # TypeError: unhashable type: 'list'
("a", {"b": "c"}): "any_type", # TypeError: unhashable type: 'dict'
("a", ("b", ["c"])): "any_type", # TypeError: unhashable type: 'list'
}

4. 自定义的类

  • 默认自定义的类都是可 hash 的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class 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
    11
    class 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
    11
    class 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
    8
    class 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
    14
    class 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