【发布时间】:2016-10-20 02:50:39
【问题描述】:
字典键可能应该是不可变的,但这不是实际上的要求。所需要的只是可以对密钥进行哈希处理。如果我更改对象以使其哈希在将其插入我的字典后发生更改会发生什么?除了一般的“坏事”之外,我还遇到了一些意想不到的行为。
>>> 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}
然后,要求IPython 显示:
>>> d
{__main__.Foo at 0xdeadbeef: 2, __main__.Foo at 0xdeadbeef: 2}
在指针和 dunderscores 中可能有点难以看到,但它认为我们字典中的 BOTH 值是 2。基于上面的堆栈跟踪,我猜这是因为它尝试使用它所引用的foo,而不是实际查看 at 我们的字典键,它......认为它知道...?并且只使用引用(对于我们当前的foo,使用foo.n=2,并且它“知道”这些值是foo.n 而不是“常规整数”)?这可能是最令人困惑的部分,我希望能得到一些理解。
最后一个问题:这是 IPython 中的错误(在这种情况下,我将尝试提交错误报告)还是使用可散列但可变的 dict 键并更改它们并将它们重新添加到字典的过程Python中的“未定义行为”?从print(d) 的输出来看,它的定义似乎非常明确,但也许我遗漏了一些东西。
【问题讨论】:
-
要求是“hashable”,即defined as “如果一个对象的哈希值在其生命周期内永远不会改变[...]”,则该对象是可哈希的。”。因此,您违反了要求。您的 "hashable-but-mutable" 是矛盾的。
-
我们通常会尝试让 IPython 处理损坏的用户代码,因此请随时提出问题。不过,我们可能会悄悄地翻白眼;-)
-
@StefanPochmann - 这可能是我一直在寻找的 - 如果我违反了要求,那么获得未定义的行为是可以的。我没有意识到这是可哈希的精确定义。谢谢!
-
@ThomasK - 鼓励翻白眼 - 这是一件特别挑剔的事情,不应该在实践中发生,所以我不会担心。我只是想了解错误/差异的原因是什么,以查明“预期行为”可能是什么。但正如 Stefan 所提到的,一旦我开始弄乱对象哈希值,我就失去了对代码有精确期望的能力。
标签: python dictionary ipython