Python 字典的键可以重复吗!

3 投票
2 回答
7391 浏览
提问于 2025-04-16 14:31

这里有个很简单的新手问题。对于一个Python字典q,len(set(q.keys())) != len(q.keys())。这可能吗?

2 个回答

1

字典和集合的底层代码基本是一样的,所以你通常可以期待 len(set(d.keys())) == len(d.keys()) 这个关系是成立的。

不过,集合和字典都依赖于 __eq__ 和 __hash__ 这两个方法来识别唯一的值,并把它们组织起来以便快速查找。所以,如果这两个方法返回的结果不一致(或者违反了“如果 a==b,那么 hash(a) 也应该等于 hash(b)”这个规则),那么就无法保证这个关系成立:

>>> from random import randrange
>>> class A():
    def __init__(self, x):
        self.x = x
    def __eq__(self, other):
        return bool(randrange(2))
    def __hash__(self):
        return randrange(8)
    def __repr__(self):
        return '|%d|' % self.x


>>> s = [A(i) for i in range(100)]
>>> d = dict.fromkeys(s)
>>> len(d.keys())
29
>>> len(set(d.keys()))
12
17

如果你违反了 dict 的某个要求,可能会出现这种情况,那就是你改变了它的哈希值。

当一个对象被用在 dict 中时,它的哈希值不能改变,而且它与其他对象的相等性也不能改变。其他属性可以改变,只要这些变化不影响它在字典中的表现。

(这并不是说哈希值就绝对不能改变,这是一个常见的误解。哈希值本身是可以改变的。只有 dict 要求键的哈希值必须保持不变,而不是说 __hash__ 本身不能改变。)

下面的代码将一个对象添加到字典中,然后在字典的背后改变了它的哈希值。 q[a] = 2 这行代码把 a 作为一个新键添加到字典中,尽管它已经存在;因为哈希值改变了,字典找不到旧的值。这就重现了你看到的奇怪现象。

class Test(object):
    def __init__(self, h):
        self.h = h
    def __hash__(self):
        return self.h

a = Test(1)
q = {}
q[a] = 1
a.h = 2
q[a] = 2

print q

# True:
print len(set(q.keys())) != len(q.keys())

撰写回答