Dict-keys应该是不可变的,但实际上不是要求。所需要的只是可以对密钥进行哈希处理。如果我在将对象插入我的dict后更改对象以使其哈希值发生变化,会发生什么?除了一般的"坏事",我还有一些意想不到的行为。
>>> class Foo(object):
def __init__(self, n):
self.n = n
def __hash__(self):
return self.n
>>> foo = Foo(1)
>>> d = {foo : foo.n}
>>> print(d)
{<__main__.Foo at 0xdeadbeef: 1}
>>> d
{<__main__.Foo at 0xdeadbeef: 1}
到目前为止,这么好。遵守规则的时间已经结束。让我们做点蠢事:
>>> foo.n += 1
现在,我正在运行IPython 5.1.0
(通过anaconda
安装),正在运行Python 3.5.2
(在Mac上?不确定哪些系统详细信息很有趣 - 请求更多信息,我很乐意添加它。)
>>> print(d)
{<__main__.Foo at 0xdeadbeef: 1}
>>> d
KeyError ...
IPython/core/formatters.py
print.pretty(obj)
IPython/lib/pretty.py
return self.type_pprinters[cls](obj, self, cycle)
IPython/lib/pretty.py
p.pretty(obj[key])
KeyError: <__main__.Foo at 0xdeadbeef>
这让我感到惊讶/困惑 - 如果我们能够正确print
该对象,为什么IPython
能够print
找出它?print(d)
似乎它试图查找密钥,当然它无法找到,因为哈希值发生了变化,但是 - 为什么>>> d[foo] = foo.n
工作正常呢?
好的,没有做蠢事:
foo
从逻辑上思考 - >>> print(d)
{__main__.Foo at 0xdeadbeef: 1, __main__.Foo at 0xdeadbeef: 2}
的哈希值发生了变化,因此它无法识别它已经拥有此密钥&#34; - 它没有已经拥有此密钥。和
IPython
然后,要求>>> d
{__main__.Foo at 0xdeadbeef: 2, __main__.Foo at 0xdeadbeef: 2}
显示:
2
在指针和dunderscores中可能有点难以看到,但它认为我们字典中的 BOTH 值为foo
。基于上面的堆栈跟踪,我猜测这是因为它尝试使用它有引用的foo
,而不是实际查找 at 我们的字典键。 ..它知道......?并且只使用引用(对于我们当前的foo.n=2
,使用foo.n
,并且&#34;知道&#34;值print(d)
不是&#34;常规整数&#34; )?这可能是最令人困惑的部分,我会欣赏一些理解。
最后一个问题:这是IPython中的一个错误(在这种情况下我会尝试提交错误报告),或者是使用hashable-but-mutable dict键并更改它们并重新添加它们的过程字典&#34;未定义的行为&#34;在Python?从FROM
的输出来看,似乎很明确,但也许我错过了一些东西。
答案 0 :(得分:0)
为了完整起见,我会在这里给出答案,以便我们可以关闭这个循环:
我的问题的开头说
“Dict-keys应该是不可变的,但实际上并非如此 需求。所需要的只是密钥可以被散列。“
但是,正如@Stefan Pochmann所指出的那样,准确度不够准确。 dict-key必须是可散列的,并且散列不应该在对象的生命周期内发生变化。
这并不意味着对象必须是不可变的,但它确实意味着提供给哈希的对象部分不应该以他们改变输出的方式改变__hash__()
电话。
所以,当我以改变哈希的方式改变我的实例时,我违反了要求并且所有的赌注都没有了 - 我再也无法对这个对象的行为方式做出预期。那么IPython的“问题”就是它对我所违反的对象(假设,而不是对象)做出有效假设的问题,所以我错误地理解它是完全可以理解的。