code: def force_hashable(obj, recursive=True):
      
      # if it's already hashable, and isn't a generator (which are also hashable, but also mutable?)
      if hasattr(obj, '__hash__') and not hasattr(obj, 'next') and not hasattr(obj, '__next__'):
          try:
              hash(obj)
              return obj
          except (IndexError, ValueError, AttributeError, TypeError):
              pass
      if hasattr(obj, '__iter__'):
          # looks like a Mapping if it has .get() and .items(), so should treat it like one
          if hasattr(obj, 'get') and hasattr(obj, 'items'):
              # FIXME: prevent infinite recursion:
              #        tuples don't have 'items' method so this will recurse forever
              #        if elements within new tuple aren't hashable and recurse has not been set!
              return force_hashable(tuple(obj.items()))
          if recursive:
              return tuple(force_hashable(item) for item in obj)
          return tuple(obj)
      # strings are hashable so this ends the recursion for any object without an __iter__ method (strings do not)
      return str(obj)
docs: Force frozenset() command to freeze the order and contents of mutables and iterables like lists, dicts, generators
  
      Useful for memoization and constructing dicts or hashtables where keys must be immutable.
  
      FIXME: Rename function because "hashable" is misleading.
             A better name might be `force_immutable`.
             because some hashable objects (generators) are tuplized  by this function
             `tuplized` is probably a better name, but strings are left alone, so not quite right
  
      >>> force_hashable([1,2.,['3','four'],'five', {'s': 'ix'}])
      (1, 2.0, ('3', 'four'), 'five', (('s', 'ix'),))
      >>> force_hashable(i for i in range(4))
      (0, 1, 2, 3)
      >>> sorted(force_hashable(Counter('abbccc'))) ==   [('a', 1), ('b', 2), ('c', 3)]
      True