Usando objetos de Python que no son hashable como claves en diccionarios

Python no permite que se utilicen objetos no hashable como claves en otros diccionarios. Como señaló Andrey Vlasovskikh, hay una buena solución para el caso especial de usar diccionarios no nesteds como claves:

frozenset(a.items())#Can be put in the dictionary instead 

¿Existe un método para usar objetos arbitrarios como claves en los diccionarios?

Ejemplo :

¿Cómo se usaría esto como una llave?

 {"a":1, "b":{"c":10}} 

Es extremadamente raro que realmente tenga que usar algo como esto en su código. Si cree que este es el caso, considere cambiar su modelo de datos primero.

Caso de uso exacto

El caso de uso es el almacenamiento en caché de llamadas a una función de palabra clave arbitraria solamente. Cada clave en el diccionario es una cadena (el nombre del argumento) y los objetos pueden ser bastante complicados, consistiendo en diccionarios por capas, listas, tuplas, etc.

Problemas relacionados

Este subproblema se ha separado del problema aquí . Las soluciones aquí tratan el caso en el que los diccionarios no están en capas.

No lo hagas Estoy de acuerdo con el comentario de Andreys sobre la pregunta anterior, que no tiene sentido tener diccionarios como claves, y especialmente no nesteds. Su modelo de datos es obviamente bastante complejo, y los diccionarios probablemente no sean la respuesta correcta. Deberías probar algo de OO en su lugar.

Basada en la solución por Chris Lutz de nuevo.

 import collections def hashable(obj): if isinstance(obj, collections.Hashable): items = obj elif isinstance(obj, collections.Mapping): items = frozenset((k, hashable(v)) for k, v in obj.iteritems()) elif isinstance(obj, collections.Iterable): items = tuple(hashable(item) for item in obj) else: raise TypeError(type(obj)) return items 

Basado en la solución de Chris Lutz. Tenga en cuenta que esto no controla los objetos que se modifican por iteración, como las secuencias, ni los ciclos.

 import collections def make_hashable(obj): """WARNING: This function only works on a limited subset of objects Make a range of objects hashable. Accepts embedded dictionaries, lists or tuples (including namedtuples)""" if isinstance(obj, collections.Hashable): #Fine to be hashed without any changes return obj elif isinstance(obj, collections.Mapping): #Convert into a frozenset instead items=list(obj.items()) for i, item in enumerate(items): items[i]=make_hashable(item) return frozenset(items) elif isinstance(obj, collections.Iterable): #Convert into a tuple instead ret=[type(obj)] for i, item in enumerate(obj): ret.append(make_hashable(item)) return tuple(ret) #Use the id of the object return id(obj) 

Si realmente debes, haz tus objetos hashable. Subclase de lo que quiera poner como clave y proporcione una función __hash__ que devuelva una clave única a este objeto.

Para ilustrar:

 >>> ("a",).__hash__() 986073539 >>> {'a': 'b'}.__hash__() Traceback (most recent call last): File "", line 1, in  TypeError: 'NoneType' object is not callable 

Si tu hash no es lo suficientemente único, obtendrás colisiones. Puede ser lento también.

Estoy totalmente en desacuerdo con los comentarios y respuestas que dicen que esto no debe hacerse por razones de pureza del modelo de datos.

Un diccionario asocia un objeto con otro objeto utilizando el anterior como clave. Los diccionarios no se pueden usar como claves porque no son hashable. Esto no hace que sea menos significativo / práctico / necesario para asignar diccionarios a otros objetos.

Como entiendo el sistema de enlace de Python, puede vincular cualquier diccionario a una serie de variables (o lo contrario, depende de su terminología), lo que significa que todas estas variables conocen el mismo “puntero” único para ese diccionario. ¿No sería posible usar ese identificador como la clave de hash? Si su modelo de datos garantiza / hace cumplir que no puede tener dos diccionarios con el mismo contenido que las claves, entonces me parece una técnica segura.

Debo agregar que no tengo idea alguna de cómo se puede / debería hacer eso.

No estoy completamente de si esto debería ser una respuesta o un comentario. Por favor corrígeme si es necesario.

¡Con recursión !

 def make_hashable(h): items = h.items() for item in items: if type(items) == dict: item = make_hashable(item) return frozenset(items) 

Puede agregar otras pruebas de tipo para cualquier otro tipo mutable que quiera hacer hashable. No debería ser difícil.

Encontré este problema al usar un decorador que almacena en caché los resultados de llamadas anteriores basadas en la firma de la llamada. No estoy de acuerdo con los comentarios / respuestas aquí sobre el efecto de “usted no debe hacer esto”, pero creo que es importante reconocer el potencial de comportamiento sorprendente e inesperado al tomar este camino. Mi idea es que, dado que las instancias son mutables y hashable, y no parece práctico cambiar eso, no hay nada intrínsecamente erróneo en la creación de equivalentes en hashable de tipos u objetos que no se pueden hacer hashable. Pero claro que esa es solo mi opinión.

Para cualquier persona que requiera compatibilidad con Python 2.5, lo siguiente puede ser útil. Lo basé en la respuesta anterior.

 from itertools import imap tuplemap = lambda f, data: tuple(imap(f, data)) def make_hashable(obj): u"Returns a deep, non-destructive conversion of given object to an equivalent hashable object" if isinstance(obj, list): return tuplemap(make_hashable, iter(obj)) elif isinstance(obj, dict): return frozenset(tuplemap(make_hashable, obj.iteritems())) elif hasattr(obj, '__hash__') and callable(obj.__hash__): try: obj.__hash__() except: if hasattr(obj, '__iter__') and callable(obj.__iter__): return tuplemap(make_hashable, iter(obj)) else: raise NotImplementedError, 'object of type %s cannot be made hashable' % (type(obj),) else: return obj elif hasattr(obj, '__iter__') and callable(obj.__iter__): return tuplemap(make_hashable, iter(obj)) else: raise NotImplementedError, 'object of type %s cannot be made hashable' % (type, obj) 

Estoy de acuerdo con Lennart Regebro en que no. Sin embargo, a menudo me resulta útil almacenar en la memoria caché algunas llamadas de funciones, objetos que se pueden llamar y / o objetos de peso mosca, ya que pueden usar argumentos de palabras clave.

Pero si realmente lo quieres, prueba pickle.dumps (o cPickle si Python 2.6) como un truco rápido y sucio. Es mucho más rápido que cualquiera de las respuestas que utiliza llamadas recursivas para hacer que los elementos sean inmutables, y las cadenas son hashable.

 import pickle hashable_str = pickle.dumps(unhashable_object)