Come esercizio, e principalmente per il mio divertimento, sto implementando un parser packrat di backtracking. L'ispirazione per questo è che mi piacerebbe avere un'idea migliore di come le macro igieniche funzionerebbero in un linguaggio simile ad algol (come in contrasto con i dialetti lisp privi di sintassi in cui li trovi normalmente). Per questo motivo, diversi passaggi attraverso l'input potrebbero visualizzare grammatiche diverse, quindi i risultati dell'analisi memorizzati nella cache non sono validi, a meno che non archivi anche la versione corrente della grammatica insieme ai risultati dell'analisi memorizzati nella cache. ( EDIT : una conseguenza di questo utilizzo delle raccolte di valori-chiave è che dovrebbero essere immutabili, ma non intendo esporre l'interfaccia per consentire la modifica, quindi le raccolte modificabili o immutabili vanno bene)
Il problema è che i dict di Python non possono apparire come chiavi per altri dict. Anche l'uso di una tupla (come farei comunque) non aiuta.
>>> cache = {}
>>> rule = {"foo":"bar"}
>>> cache[(rule, "baz")] = "quux"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>>
Immagino che debbano essere tuple fino in fondo. Ora la libreria standard di Python fornisce approssimativamente ciò di cui avrei bisogno, collections.namedtuple
ha una sintassi molto diversa, ma può essere utilizzata come chiave. continuando dalla sessione precedente:
>>> from collections import namedtuple
>>> Rule = namedtuple("Rule",rule.keys())
>>> cache[(Rule(**rule), "baz")] = "quux"
>>> cache
{(Rule(foo='bar'), 'baz'): 'quux'}
Ok. Ma devo creare una classe per ogni possibile combinazione di chiavi nella regola che vorrei usare, il che non è poi così male, perché ogni regola di analisi sa esattamente quali parametri utilizza, in modo che quella classe possa essere definita allo stesso tempo come la funzione che analizza la regola.
Modifica: un ulteriore problema con namedtuple
s è che sono strettamente posizionali. Due tuple che sembrano dovrebbero essere diverse possono infatti essere le stesse:
>>> 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: Come ottengo dict
i messaggi che possono essere usati come chiavi per altri messaggi dict
?
Dopo aver modificato un po 'le risposte, ecco la soluzione più completa che sto usando. Nota che questo fa un po 'di lavoro extra per rendere i dict risultanti vagamente immutabili per scopi pratici. Ovviamente è ancora abbastanza facile aggirarlo chiamando, dict.__setitem__(instance, key, value)
ma qui siamo tutti adulti.
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 essere immutabile, almeno dopo aver avviato l'hashing, quindi perché non memorizzare nella cache i valorikey
ehash
come attributihashdict
dell'oggetto? Ho modificato__key()
e__hash__()
, e testato per confermare che è molto più veloce. SO non consente il codice formattato nei commenti, quindi lo collegherò qui: sam.aiki.info/hashdict.py