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 sobre cómo funcionarían las macros higiénicas en un lenguaje similar al algol (en oposición a los dialectos lisp sin sintaxis en los que normalmente se encuentran). Debido a esto, diferentes pases a través de la entrada pueden ver diferentes gramáticas, por lo que los resultados del 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 del análisis en caché. ( EDITAR : una consecuencia de este uso de colecciones de valores clave es que deberían ser inmutables, pero no tengo la intención de exponer la interfaz para permitir que se cambien, por lo que las colecciones mutables o inmutables están bien)
El problema es que los dictados de Python no pueden aparecer como claves para otros dictados. Incluso usar una tupla (como 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 hasta el final. Ahora, la biblioteca estándar de Python proporciona aproximadamente lo que necesitaría, collections.namedtuple
tiene una sintaxis muy diferente, pero se puede usar 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'}
Okay. Pero tengo que hacer una clase para cada combinación posible de claves en la regla que me gustaría usar, lo cual no es tan malo, porque cada regla de análisis sabe exactamente qué parámetros usa, por lo que esa 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. De hecho, dos tuplas que parecen ser diferentes pueden ser iguales:
>>> 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 obtengo mensajes de correo electrónico dict
que se pueden usar como claves para otros mensajes de correo electrónico dict
?
Habiendo pirateado 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 hacer que los dictados resultantes sean vagamente inmutables para fines prácticos. Por supuesto, todavía es bastante fácil solucionarlo llamando, 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()
hashdict
debe ser inmutable, al menos después de comenzar a aplicar el hash, entonces, ¿por qué no almacenar en caché los valoreskey
yhash
como atributos delhashdict
objeto? Modifiqué__key()
y__hash__()
probé para confirmar que es mucho más rápido. SO no permite código formateado en los comentarios, así que lo vincularé aquí: sam.aiki.info/hashdict.py