Como ejercicio, y sobre todo para mi propia diversión, estoy implementando un analizador packrat de retroceso. La inspiración para esto es que me gustaría tener una mejor idea acerca de cómo funcionarían las macros higiénicas en un lenguaje similar a algol (como en el dialecto de lisp libre de sintaxis en el que normalmente las encuentras). Debido a esto, las diferentes pasadas a través de la entrada pueden ver diferentes gramáticas, por lo que los resultados de análisis en caché no son válidos, a menos que también almacene la versión actual de la gramática junto con los resultados de análisis en caché. (EDIT: una consecuencia de este uso de colecciones de clave-valor es que deben ser inmutables, pero no pretendo exponer la interfaz para permitir su modificación, por lo que las colecciones mutables o inmutables son correctas)Python hasts intercambiables
El problema es que los dictados de python no pueden aparecer como claves para otros dicts. Incluso usar una tupla (como lo estaría haciendo de todos modos) no ayuda.
>>> cache = {}
>>> rule = {"foo":"bar"}
>>> cache[(rule, "baz")] = "quux"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>>
Supongo que tiene que ser tuplas todo el camino hacia abajo. Ahora la biblioteca estándar de Python proporciona aproximadamente lo que necesitaría, collections.namedtuple
tiene una sintaxis muy diferente, pero puede usarse como clave. continuando desde la sesión anterior:
>>> from collections import namedtuple
>>> Rule = namedtuple("Rule",rule.keys())
>>> cache[(Rule(**rule), "baz")] = "quux"
>>> cache
{(Rule(foo='bar'), 'baz'): 'quux'}
Ok. Pero tengo que hacer una clase para cada posible combinación de claves en la regla que quisiera usar, lo cual no es tan malo, ya que cada regla de análisis sabe exactamente qué parámetros usa, por lo que la clase se puede definir al mismo tiempo como la función que analiza la regla.
Editar: Un problema adicional con namedtuple
s es que son estrictamente posicionales. Dos tuplas que parecen que deben ser diferentes de hecho, puede ser el mismo:
>>> you = namedtuple("foo",["bar","baz"])
>>> me = namedtuple("foo",["bar","quux"])
>>> you(bar=1,baz=2) == me(bar=1,quux=2)
True
>>> bob = namedtuple("foo",["baz","bar"])
>>> you(bar=1,baz=2) == bob(bar=1,baz=2)
False
tl'dr: ¿Cómo llego dict
s que pueden ser utilizados como claves para otros dict
s?
Habiendo hackeado un poco las respuestas, esta es la solución más completa que estoy usando. Tenga en cuenta que esto hace un poco más de trabajo para que los dictados resultantes sean vagamente inmutables con fines prácticos. Por supuesto, todavía es bastante fácil hackearlo llamando al dict.__setitem__(instance, key, value)
, pero todos somos adultos aquí.
class hashdict(dict):
"""
hashable dict implementation, suitable for use as a key into
other dicts.
>>> h1 = hashdict({"apples": 1, "bananas":2})
>>> h2 = hashdict({"bananas": 3, "mangoes": 5})
>>> h1+h2
hashdict(apples=1, bananas=3, mangoes=5)
>>> d1 = {}
>>> d1[h1] = "salad"
>>> d1[h1]
'salad'
>>> d1[h2]
Traceback (most recent call last):
...
KeyError: hashdict(bananas=3, mangoes=5)
based on answers from
http://stackoverflow.com/questions/1151658/python-hashable-dicts
"""
def __key(self):
return tuple(sorted(self.items()))
def __repr__(self):
return "{0}({1})".format(self.__class__.__name__,
", ".join("{0}={1}".format(
str(i[0]),repr(i[1])) for i in self.__key()))
def __hash__(self):
return hash(self.__key())
def __setitem__(self, key, value):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def __delitem__(self, key):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def clear(self):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def pop(self, *args, **kwargs):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def popitem(self, *args, **kwargs):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def setdefault(self, *args, **kwargs):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def update(self, *args, **kwargs):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
# update is not ok because it mutates the object
# __add__ is ok because it creates a new object
# while the new object is under construction, it's ok to mutate it
def __add__(self, right):
result = hashdict(self)
dict.update(result, right)
return result
if __name__ == "__main__":
import doctest
doctest.testmod()
El 'hashdict' debe ser inmutable, al menos después de empezar a hash, así que por qué no almacenar en caché el' 'key' y los valores hash' como atributos del objeto' hashdict'? Modifiqué '__key()' y '__hash __()', y probé para confirmar que es mucho más rápido. SO no permite el código formateado en los comentarios, así que lo vincularé aquí: http://sam.aiki.info/hashdict.py –
"Estos programadores están locos". - Obelix –