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.