IPython, mutable dict keys, possible insanity

淺唱寂寞╮ 提交于 2019-12-25 08:58:46

问题


Dict-keys should probably be immutable, but that's not actually a requirement. All that's required is that the key can be hashed. What happens if I change the object in such a way that its hash changes after inserting it into my dict? Besides just generally "bad things", I'm getting some extra-unexpected behavior.

>>> 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}

so far, so good. The time for following rules is over. Let's do something dumb:

>>> foo.n += 1

Now, I'm running IPython 5.1.0 (installed via anaconda), which is running Python 3.5.2 (on a Mac? not sure yet which system details are interesting - ask for more info and I'll be happy to add it).

>>> 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>

This is surprising/confusing to me - if we can correctly print the object, why can't IPython figure out how to print it? It seems like it's trying to look up the key, which of course it can't find because the hash changed, but - why then does print(d) work just fine?

Ok, not done being dumb:

>>> d[foo] = foo.n

Logically thinking - the hash of foo changed so it'll not recognize that it "already has this key" - it doesn't already have this key. And:

>>> print(d)
{__main__.Foo at 0xdeadbeef: 1, __main__.Foo at 0xdeadbeef: 2}

but then, asking IPython to show:

>>> d
{__main__.Foo at 0xdeadbeef: 2, __main__.Foo at 0xdeadbeef: 2}

Might be a little hard to see amidst the pointers and dunderscores, but it thinks BOTH values in our dictionary are 2. Based on the stacktrace above, I'm guessing this is because it tries to use the foo it has a reference to, and instead of actually looking at our dictionary keys, it ...thinks it knows...? And just uses the reference (for our current foo, with foo.n=2, and it "knows" the values are foo.n not "regular integers")? This is probably the most baffling part, and where I would appreciate some understanding.

Final question: is this a bug in IPython (in which case I'll try to file a bug-report) or is the process of using hashable-but-mutable dict keys and changing them and re-adding them to dictionaries "undefined behavior" in Python? It seems pretty well-defined in terms of output from print(d), but maybe I'm missing something.


回答1:


For the sake of completeness, I'll put an answer here so we can close this loop:

The beginning of my question said

"Dict-keys should probably be immutable, but that's not actually a requirement. All that's required is that the key can be hashed."

But, as pointed out by @Stefan Pochmann, that's not precise enough to be accurate. The dict-key must be hashable and the hash should never change over the object's lifetime.

That doesn't mean the object must be immutable, but it does mean the parts of the object that feed into the hash shouldn't change in such a way that they'd change the output of the __hash__() call.

So, when I altered my instance in such a way as to change the hash, I violated the requirement and all bets are off - I can no longer make expectations about how this object will act. The "issue" with IPython then is just a matter of it making valid assumptions about objects that I've violated (the assumptions, not the objects), so it's perfectly understandable for it to error out on me.



来源:https://stackoverflow.com/questions/40144589/ipython-mutable-dict-keys-possible-insanity

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!