我能在Python中使用可变对象作为字典键,这不是被禁止的吗?

23 投票
4 回答
12945 浏览
提问于 2025-04-16 08:24
class A(object):
    x = 4

i = A()
d = {}

d[i] = 2

print d

i.x = 10

print d

我原以为只有不可变的对象才能作为字典的键,但上面这个对象是可变的。

4 个回答

8

一个对象可以作为字典的键,前提是它是可哈希的

这里是文档中对可哈希的定义:

一个对象是可哈希的,如果它有一个在其生命周期内永远不变的哈希值(这需要一个__hash__()方法),并且可以与其他对象进行比较(这需要一个__eq__()__cmp__()方法)。那些比较相等的可哈希对象必须具有相同的哈希值。

可哈希性使得一个对象可以用作字典的键和集合的成员,因为这些数据结构在内部使用哈希值。

Python中所有不可变的内置对象都是可哈希的,而可变的容器(比如列表或字典)则不是。用户自定义类的实例默认是可哈希的;它们之间比较总是不相等,并且它们的哈希值是它们的id()。

由于object提供了__hash____eq____cmp__的默认实现,这意味着任何从object派生的东西都是可哈希的,除非它被明确地定义为不可哈希。创建一个可哈希的可变类型并不是不允许的,但它可能不会按你想要的方式工作。

11

@fred-nurk 上面的例子在 Python 3 中幸运地不再有效,这是因为 这个变化

如果一个类重写了 __eq__() 方法,但没有定义 __hash__() 方法,那么它的 __hash__() 方法会被隐式设置为 None。当一个类的 __hash__() 方法是 None 时,如果程序试图获取这个类的实例的哈希值,就会引发一个合适的 TypeError 错误……

感谢上天有这个变化。不过,如果你自己明确地定义了 __hash__() 方法,那么你仍然可以做一些不好的事情:

class BadHasher:
    def __init__(self):
        self.first = True

    # Implement __hash__ in an evil way. The first time an instance is hashed,
    # return 1. Every time after that, return 0.
    def __hash__(self):
        if self.first:
            self.first = False
            return 1
        return 0

myobject = BadHasher()
# We can put this object in a set...
myset = {myobject}
# ...but as soon as we look for it, it's gone!
if myobject not in myset:
    print("what the hell we JUST put it in there")
25

任何有 __hash__ 方法的对象都可以作为字典的键。对于你自己写的类,这个方法默认会返回一个基于 id(self) 的值。如果在这些类中,判断相等性不是通过身份来决定的,你可能会在用它们作为键的时候感到意外:

>>> class A(object):
...   def __eq__(self, other):
...     return True
... 
>>> one, two = A(), A()
>>> d = {one: "one"}
>>> one == two
True
>>> d[one]
'one'
>>> d[two]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: <__main__.A object at 0xb718836c>

>>> hash(set())  # sets cannot be dict keys
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'

在2.6版本中进行了更改:__hash__ 现在可以设置为 None,以明确标记类的实例为不可哈希的。[__hash__]

class Unhashable(object):
  __hash__ = None

撰写回答