tags:

views:

121

answers:

4

I am interested in taking an arbitrary dict and copying it into a new dict, mutating it along the way.

One mutation I would like to do is swap keys and value. Unfortunately, some values are dicts in their own right. However, this generates a "unhashable type: 'dict'" error. I don't really mind just stringifying the value and giving it the key. But, I'd like to be able to do something like this:

for key in olddict:
  if hashable(olddict[key]):
    newdict[olddict[key]] = key
  else
    newdict[str(olddict[key])] = key

Is there a clean way to do this that doesn't involve trapping an exception and parsing the message string for "unhashable type" ?

+1  A: 

Similar questions have been asked before. This answer may help you.

Manoj Govindan
I'm not interested in the conversion process so mush as I want to be able to say "Is hashable? Yes? Use as key. No? Okay, become a string and then become a key"
Paul Nathan
@Paul: Fair enough. I thought you might want to look at alternate approaches as well.
Manoj Govindan
@Paul: Manoj's second link does include code for checking if something is hashable, though that part of the code is reproduced in Mark Byers' answer to your question.
Brian
+2  A: 
def hashable(v):
    """Determine whether `v` can be hashed."""
    try:
        hash(v)
    except TypeError:
        return False
    return True
Ned Batchelder
Ned - that's exactly what I would prefer to avoid. Also, this function will trap TypeErrors that aren't "unhashable type".
Paul Nathan
There's only one TypeError that hash() will raise. Or alternately, whatever TypeError hash raises, it's going to keep your value from being hashed. I'd even argue that this should catch Exception, because it doesn't really matter why hash() failed: a failure in hash() makes your value unhashable. Can you say more about why you want to avoid exceptions like this? This encapsulates the exception handling in a nice function, and makes your sample code above work perfectly.
Ned Batchelder
@Ned: Perhaps it's more of a philosophical point - the question isn't about the "exceptional condition" of being unhashable due to some sort of "freak condition", but a query about the functionality available on a type. If that makes any sense.
Paul Nathan
I think you're right about it being a philosophical point, and perhaps comes down to a static vs. dynamic view of the world. The classic Python mindset often tries things and catches exceptions, rather than attempting to determine upfront if something in theory is possible.
Ned Batchelder
@Paul Nathan: This is the **standard** approach. Simply attempt the operation; if it fails, then the object was not hashable; do something else. If it works, then the object was hashable and you did what you expected to do.
S.Lott
+4  A: 

Since Python 2.6 you can use the abstract base class collections.Hashable:

import collections
>>> isinstance({}, collections.Hashable)
False
>> isinstance(0, collections.Hashable)
True

This approach is also mentioned briefly in the documentation for __hash__.

Doing so means that not only will instances of the class raise an appropriate TypeError when a program attempts to retrieve their hash value, but they will also be correctly identified as unhashable when checking isinstance(obj, collections.Hashable) (unlike classes which define their own __hash__() to explicitly raise TypeError).

Mark Byers
+1  A: 

All hashable built in python objects have a .__hash__() method. You can check for that.

olddict = {"a":1, "b":{"test":"dict"}, "c":"string", "d":["list"] }

for key in olddict:
   if(olddict[key].__hash__):
      print str(olddict[key]) + " is hashable"
   else: 
      print str(olddict[key]) + " is NOT hashable"

output

1 is hashable
string is hashable
{'test': 'dict'} is NOT hashable
['list'] is NOT hashable
Chandler
A warning about this: In Python 2.5 this will give: `{'test': 'dict'}is hashable`. It can also give a wrong result in newer versions if a class defines `__hash__` to raise a TypeError.
Mark Byers