Als Übung und hauptsächlich zu meiner eigenen Unterhaltung implementiere ich einen Backtracking-Packrat-Parser. Die Inspiration dafür ist, dass ich eine bessere Vorstellung davon haben möchte, wie hygienische Makros in einer algolähnlichen Sprache funktionieren würden (im Gegensatz zu den syntaxfreien Lisp-Dialekten, in denen sie normalerweise vorkommen). Aus diesem Grund werden bei unterschiedlichen Durchläufen durch die Eingabe möglicherweise unterschiedliche Grammatiken angezeigt, sodass zwischengespeicherte Analyseergebnisse ungültig sind, es sei denn, ich speichere auch die aktuelle Version der Grammatik zusammen mit den zwischengespeicherten Analyseergebnissen. ( BEARBEITEN : Eine Konsequenz dieser Verwendung von Schlüsselwertsammlungen ist, dass sie unveränderlich sein sollten, aber ich beabsichtige nicht, die Schnittstelle verfügbar zu machen, damit sie geändert werden können, sodass entweder veränderbare oder unveränderliche Sammlungen in Ordnung sind.)
Das Problem ist, dass Python-Dikte nicht als Schlüssel für andere Dikte angezeigt werden können. Selbst die Verwendung eines Tupels (wie ich es sowieso tun würde) hilft nicht.
>>> cache = {}
>>> rule = {"foo":"bar"}
>>> cache[(rule, "baz")] = "quux"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>>
Ich denke, es müssen ganz unten Tupel sein. Jetzt bietet die Python-Standardbibliothek ungefähr das, was ich brauche, collections.namedtuple
hat eine ganz andere Syntax, kann aber als Schlüssel verwendet werden. Fortsetzung der obigen Sitzung:
>>> from collections import namedtuple
>>> Rule = namedtuple("Rule",rule.keys())
>>> cache[(Rule(**rule), "baz")] = "quux"
>>> cache
{(Rule(foo='bar'), 'baz'): 'quux'}
OK. Aber ich muss eine Klasse für jede mögliche Kombination von Schlüsseln in der Regel erstellen, die ich verwenden möchte, was nicht so schlimm ist, da jede Analyseregel genau weiß, welche Parameter sie verwendet, damit die Klasse gleichzeitig definiert werden kann als die Funktion, die die Regel analysiert.
Bearbeiten: Ein zusätzliches Problem mit namedtuple
s ist, dass sie streng positionell sind. Zwei Tupel, die so aussehen, als ob sie unterschiedlich sein sollten, können tatsächlich gleich sein:
>>> 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: Wie bekomme ich dict
s, die als Schlüssel für andere verwendet werden können ?dict
s verwendet werden können?
Nachdem ich die Antworten ein wenig gehackt habe, ist hier die vollständigere Lösung, die ich verwende. Beachten Sie, dass dies ein wenig zusätzliche Arbeit leistet, um die resultierenden Diktate für praktische Zwecke vage unveränderlich zu machen. Natürlich ist es immer noch recht einfach, sich durch einen Anruf darum zu kümmern, dict.__setitem__(instance, key, value)
aber wir sind alle Erwachsene hier.
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
muss unveränderlich sein, zumindest nachdem Sie mit dem Hashing begonnen haben. Warum also nicht die Wertekey
undhash
als Attribute deshashdict
Objekts zwischenspeichern? Ich habe__key()
und modifiziert und__hash__()
getestet, um zu bestätigen, dass es viel schneller ist. SO erlaubt keinen formatierten Code in Kommentaren, also werde ich ihn hier verlinken