Python中能否使用自定义类作为字典的Key?

class Key:
def init(self, i, j):
self.i = i
self.j = j
pass

dic={};

key1 = Key(1, 1)
key2 = Key(1, 1)

dic.append(key1, 1)
dic.append(key2, 1)
Python中能否使用自定义类作为字典的Key?

23 回复

那要 key 来干嘛?


当然可以。在Python中,自定义类完全可以作为字典的键(Key),但有一个核心前提:你的类必须是“可哈希的”(hashable)

要让一个类变得可哈希,你需要满足两个条件:

  1. 实现 __hash__ 方法:这个方法必须返回一个整数,并且对于在程序生命周期内“相等”的对象,其哈希值必须始终相同。
  2. 实现 __eq__ 方法:这个方法用于判断两个对象是否相等。如果 a == b 为真,那么必须保证 hash(a) == hash(b)

一个非常常见且推荐的做法是,使用类的关键属性(通常是那些用于判断对象是否相等的属性)来生成哈希值。dataclassesnamedtuple 会自动为你实现这些方法,非常方便。

代码示例:

from dataclasses import dataclass

# 方法1:使用 @dataclass(frozen=True) (推荐,简单安全)
@dataclass(frozen=True)  # frozen=True 使实例不可变,这是成为可哈希键的最佳实践
class Person:
    name: str
    employee_id: int

# 方法2:手动实现 __hash__ 和 __eq__
class ManualPerson:
    def __init__(self, name: str, employee_id: int):
        self.name = name
        self.employee_id = employee_id

    def __hash__(self):
        # 使用对应属性的元组来生成哈希值
        return hash((self.name, self.employee_id))

    def __eq__(self, other):
        if not isinstance(other, ManualPerson):
            return False
        return (self.name, self.employee_id) == (other.name, other.employee_id)

# 使用示例
alice = Person("Alice", 123)
bob = ManualPerson("Bob", 456)

employee_data = {}
employee_data[alice] = "Engineer"
employee_data[bob] = "Manager"

print(employee_data[alice])  # 输出: Engineer
print(employee_data[bob])    # 输出: Manager

# 即使创建一个新实例,只要关键属性相同,依然能作为相同的键访问
alice2 = Person("Alice", 123)
print(employee_data[alice2]) # 输出: Engineer (成功访问)
print(alice == alice2)       # 输出: True (__eq__ 生效)

关键点总结:

  • 核心:实现 __hash____eq__ 方法。
  • 最佳实践:使用 @dataclass(frozen=True) 或让关键属性不可变,这样可以保证哈希值在对象生命周期内永不改变。如果对象是可变的(比如包含列表属性),并将其用作键,然后修改了它,你将无法在字典中再次找到它,这会导致极其隐蔽的bug。

一句话建议:用 @dataclass(frozen=True) 定义作为键的类,最省事也最安全。

当然可以了
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32
Type “help”, “copyright”, “credits” or “license” for more information.
>>> class A:
… pass

>>> {A():1,A():2}
{<main.A object at 0x0000026C84DA32E8>: 1, <main.A object at 0x0000026C84DA3320>: 2}
>>>

字典的 key 是不可变,能 hash 的, 你去看字典 key 的限制,只要符合规范当然可以

  1. 代码能不能跑?跑一下就知道了。
    2. 为什么能(不能)跑?参考 3 楼的答案。

能 hash 的都能.


怎么处理碰撞?
当类的 i,j 字段不一样 但 hash 又一样时,怎么处理?
需要重写 HashCode?或者 Equals 方法吗?

def hash(self): 和 eq ??

你输了五毛

汽车能当钥匙使吗?能啊。但是有些浪费。

hash 是辅助判断的,主要是 eq。要确保 eq 的两个对象 hash 必须相等,反之不一定。
最坏情况,hash 全部返回 0 都没关系,只不过会导致速度很慢。
反之如果 hash 不等但是 eq 的话就会出问题。比如用 id 做 hash 然后用成员变量值判断 eq。

hash 只是为了快速判断
当 hash 一样 会使用 eq 再判断
是吧

你竟然没碰到过必须要使用类或者结构体作 key 的场景?

一般这样的需求,我都是直接把数据套入现成可 hash 化容器的,比如 tuple, namedtuple ;其实类作为 key 也是为了对其成员变量做个区分吧



我猜测 tuple 并不会处理 def hash(self): 和 eq
然后是使用 tuple 的地址作 key 这样有可能会出问题的

python 的 dict 并不考虑你的__hash__和__eq__,他是直接比较内存地址的

刚才没注意,python 的 dict 会考虑__eq__的,测试方法如下
>>> class A:
… def hash():
… return 1

>>>
>>> class A:
… def hash(self):
… return 1
… def eq(self,a):
… return True

>>> {A():1,A():2}
{<main.A object at 0x0000026C84DA3D30>: 2}
>>> class A:
… def hash(self):
… return 1

>>> {A():1,A():2}
{<main.A object at 0x0000026C84DA3320>: 1, <main.A object at 0x0000026C84DA3D68>: 2}
>>>

A dictionary ’ s keys are almost arbitrary values. Values that are not hashable, that is, values containing lists, dictionaries or other mutable types (that are compared by value rather than by object identity) may not be used as keys.

hashable
An object is hashable if it has a hash value which never changes during its lifetime (it needs a hash() method), and can be compared to other objects (it needs an eq() or cmp() method). Hashable objects which compare equal must have the same hash value.

Hashability makes an object usable as a dictionary key and a set member, because these data structures use the hash value internally.

All of Python ’ s immutable built-in objects are hashable, while no mutable containers (such as lists or dictionaries) are. Objects which are instances of user-defined classes are hashable by default; they all compare unequal (except with themselves), and their hash value is derived from their id().

没遇到过。并且你这个只是实例做 key。

key 应该尽量简化,value 则可以复杂些。个人愚见。

哈哈, 但实际上 tuple 是 python 文档里面推荐的可 hash 可对比的不变容器,用来做 Key 合适不过了。

不过你一定要确认的话,tuple 实现都是 C 代码,要看去看解释器源码了。

>>> from collections.abc import Hashable
>>> mutable = [list, bytearray, set, dict]
>>> immutable = [int, float, complex, str, tuple, frozenset, bytes]
>>> all(isinstance(x(), Hashable) for x in immutable)
True
>>> all(issubclass(x, Hashable) for x in immutable)
True
>>> any(isinstance(x(), Hashable) for x in mutable)
False
>>> any(issubclass(x, Hashable) for x in mutable)

基本类型里,不可变的都是可 hash 的。无论是有序的 Tuple 还是无序的 frozenset,因为其是不可变的,都是可 hash 的。所以都可以当 mapping 的 key。
若是要自定义可 hash 的对象,就一定要实现__hash__、eq__两个方法。
若是不实现,也可以 hash。这是因为类缺省__hash
、__eq__两个方法,但是其依据是 id 值,那么结果就不是我们想要的。

参考:
https://wiki.python.org/moin/DictionaryKeys#User_Defined_Types_as_Dictionary_Keys
http://www.laurentluce.com/posts/python-dictionary-implementation/

回到顶部