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