我能在Python中使用可变对象作为字典键,这不是被禁止的吗?
class A(object):
x = 4
i = A()
d = {}
d[i] = 2
print d
i.x = 10
print d
我原以为只有不可变的对象才能作为字典的键,但上面这个对象是可变的。
4 个回答
一个对象可以作为字典的键,前提是它是可哈希的。
这里是文档中对可哈希的定义:
一个对象是可哈希的,如果它有一个在其生命周期内永远不变的哈希值(这需要一个
__hash__()
方法),并且可以与其他对象进行比较(这需要一个__eq__()
或__cmp__()
方法)。那些比较相等的可哈希对象必须具有相同的哈希值。可哈希性使得一个对象可以用作字典的键和集合的成员,因为这些数据结构在内部使用哈希值。
Python中所有不可变的内置对象都是可哈希的,而可变的容器(比如列表或字典)则不是。用户自定义类的实例默认是可哈希的;它们之间比较总是不相等,并且它们的哈希值是它们的id()。
由于object
提供了__hash__
、__eq__
和__cmp__
的默认实现,这意味着任何从object
派生的东西都是可哈希的,除非它被明确地定义为不可哈希。创建一个可哈希的可变类型并不是不允许的,但它可能不会按你想要的方式工作。
@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")
任何有 __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