The catch is that the unhashable objects aren't actually stored in the mapping. Only their IDs are. Thus you must also store the actual objects somewhere else.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | try:
from collections.abc import MutableMapping
except ImportError:
from collections import MutableMapping
class IDKeyedMapping(MutableMapping, dict):
'''A dict that can take mutable objects as keys.'''
def __len__(self):
return dict.__len__(self)
def __iter__(self):
return dict.__iter__(self)
def __contains__(self, key):
return dict.__contains__(self, id(key))
def __getitem__(self, key):
return dict.__getitem__(self, id(key))
def __setitem__(self, key, value):
dict.__setitem__(self, id(key), value)
def __delitem__(self, key):
dict.__delitem__(self, id(key))
def values(self):
return dict.values(self)
def items(self):
return dict.items(self)
|
Alterately, implement __hash__() on your mutable object's class:
class MyType: # Python 3 implicit base: object
def __hash__(self):
return id(self)
If you want to use a type you don't control, subclass it:
class NewType(original_type):
def __hash__(self):
return id(self)
If that doesn't work (as is the case with a very few built-in types) or you have an existing object, you could write a simple wrapper class that also implements __hash__().
This is very ingenious, but I wouldn't want to touch it with a ten foot pole for production code. Trying to debug a problem with code using this class would be a sheer nightmare, and the risk of surprising behaviour is way too high.
Surprise #1: equal keys don't work.
Surprise #2: mapping.keys() and items() don't give you the keys you care about.
Surprise #3: if you delete the real key, you can get a match by accident with the wrong key.
(At least in CPython, in Jython IDs are guaranteed to never be reused.)
There may be other surprises I haven't thought of.
Oh yeah. I wouldn't use it in production either. :) I expect you could iron out the wrinkles, but there are better solutions.