Asking "is hashable" about a Python value

23,557

Solution 1

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

Solution 2

def hashable(v):
    """Determine whether `v` can be hashed."""
    try:
        hash(v)
    except TypeError:
        return False
    return True

Solution 3

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

Solution 4

Why not use duck typing?

for key in olddict:
   try:
       newdict[olddict[key]] = key
   except TypeError:
       newdict[str(olddict[key])] = key
Share:
23,557
Paul Nathan
Author by

Paul Nathan

Software engineer/craftman/programmer Passion for quality and correctness as exemplified in the continuous improvement/kaizen model. Obsessive about tools and infrastructure Focused on working in Scala/Rust/Lisp/Haskell/OCaml Articulate Lisp - a Common Lisp environment tutorial site. Learn Common Lisp today!

Updated on July 09, 2022

Comments

  • Paul Nathan
    Paul Nathan almost 2 years

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

  • Paul Nathan
    Paul Nathan almost 14 years
    Ned - that's exactly what I would prefer to avoid. Also, this function will trap TypeErrors that aren't "unhashable type".
  • yantrab
    yantrab almost 14 years
    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.
  • Paul Nathan
    Paul Nathan almost 14 years
    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.
  • yantrab
    yantrab almost 14 years
    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.
  • user1066101
    user1066101 almost 14 years
    @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.
  • Mark Byers
    Mark Byers almost 14 years
    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.
  • Joshua Chia
    Joshua Chia about 10 years
    No, and this is something you can determine only at run-time since before that the content of a list is generally unknown. hash(([],)) gives TypeError: unhashable type: 'list'
  • RedX
    RedX over 7 years
    Small warning for people using Python 2.7 isinstance(bytearray([0xa]), collections.Hashable)) returns True but hash(bytearray([0xa])) fails with TypeError: unhashable type: 'bytearray'.
  • codykochmann
    codykochmann almost 7 years
    Anyone have any explanations for this one? isinstance(Decimal('sNaN'),Hashable) returns True yet when its given to a dictionary, I get Cannot hash a signaling NaN value
  • martineau
    martineau over 5 years
    In Python 3 it's isinstance(obj, collections.abc.Hashable).
  • David Lord
    David Lord over 4 years
    In Python 3.7.5, importing directly from collections raises DeprecationWarning. Instead, from collections.abc import Hashable.
  • Kamyar
    Kamyar about 4 years
    This method is not good practice! Using exception for checking such things is discouraged because of stack walking and so
  • Cecil Curry
    Cecil Curry about 4 years
    This is awful. The hash() builtin implicitly calls the user-defined v.__hash__() dunder method, which could conceivably raise any possible exception – not simply TypeError exceptions. To generalize this, replace the overly specific subclass TypeError above with the catch-all exception superclass Exception.
  • Cecil Curry
    Cecil Curry about 4 years
    This is awful. isinstance(obj, collections.abc.Hashable) returns false positives for user-defined classes that raise exceptions from overridden __hash__() dunder methods. collections.abc.Hashable cannot be trusted for real-world hashable detection. Instead, the only sane solution is to attempt to hash(obj) and report False if doing so raises any exceptions whatsoever.
  • Cecil Curry
    Cecil Curry about 4 years
    This is awful, too. Simply because a type defines a __hash__() method does not mean that all possible instances of that type are hashable. Consider def __hash__(self): raise ValueError('uhoh'), for example. Instead, the only sane solution is to attempt to hash(olddict[key]) and report False if doing so raises any exceptions whatsoever.
  • Rylan Schaeffer
    Rylan Schaeffer about 2 years
    Can the author please update this answer?