Como exercício, e principalmente para minha própria diversão, estou implementando um analisador packrat retroativo. A inspiração para isso é que eu gostaria de ter uma ideia melhor sobre como as macros higiênicas funcionariam em uma linguagem semelhante ao algol (em comparação com os dialetos lisp livres de sintaxe em que você normalmente os encontra). Por causa disso, passagens diferentes pela entrada podem ver gramáticas diferentes, portanto, os resultados da análise em cache são inválidos, a menos que eu também armazene a versão atual da gramática junto com os resultados da análise em cache. ( EDITAR : uma consequência desse uso de coleções de valores-chave é que elas devem ser imutáveis, mas não pretendo expor a interface para permitir que sejam alteradas, portanto, coleções mutáveis ou imutáveis são adequadas)
O problema é que dicts python não podem aparecer como chaves para outros dicts. Mesmo usar uma tupla (como eu faria de qualquer maneira) não ajuda.
>>> cache = {}
>>> rule = {"foo":"bar"}
>>> cache[(rule, "baz")] = "quux"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>>
Eu acho que tem que ser tuplas até o fim. Agora, a biblioteca padrão do python fornece aproximadamente o que eu preciso, collections.namedtuple
tem uma sintaxe muito diferente, mas pode ser usada como uma chave. continuando da sessão acima:
>>> from collections import namedtuple
>>> Rule = namedtuple("Rule",rule.keys())
>>> cache[(Rule(**rule), "baz")] = "quux"
>>> cache
{(Rule(foo='bar'), 'baz'): 'quux'}
Está bem. Mas eu tenho que fazer uma classe para cada combinação possível de chaves na regra que eu gostaria de usar, o que não é tão ruim, porque cada regra de análise sabe exatamente quais parâmetros ela usa, para que essa classe possa ser definida ao mesmo tempo como a função que analisa a regra.
Edit: Um problema adicional com namedtuple
s é que eles são estritamente posicionais. Duas tuplas que parecem que deveriam ser diferentes podem na verdade ser as mesmas:
>>> 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: Como obtenho dict
s que podem ser usados como chaves para outros dict
s?
Tendo hackeado um pouco as respostas, aqui está a solução mais completa que estou usando. Observe que isso faz um trabalho um pouco extra para tornar os dictos resultantes vagamente imutáveis para fins práticos. Claro que ainda é muito fácil contornar isso ligando, dict.__setitem__(instance, key, value)
mas somos todos adultos aqui.
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
deve ser imutável, pelo menos depois de iniciar o hash, então por que não armazenar em cache os valoreskey
ehash
como atributos dohashdict
objeto? Modifiquei__key()
e__hash__()
e testei para confirmar que é muito mais rápido. O SO não permite código formatado nos comentários, então vou vinculá-lo aqui: sam.aiki.info/hashdict.py