Jako ćwiczenie, a przede wszystkim dla własnej rozrywki, implementuję parser z wycofywaniem się. Inspiracją do tego jest to, że chciałbym mieć lepsze wyobrażenie o tym, jak higieniczne makra będą działać w języku podobnym do algolu (w odniesieniu do dialektów lisp wolnych od składni, w których zwykle je znajdziesz). Z tego powodu różne przejścia przez dane wejściowe mogą widzieć różne gramatyki, więc buforowane wyniki analizy są nieprawidłowe, chyba że przechowuję również bieżącą wersję gramatyki wraz z buforowanymi wynikami analizy. ( EDYCJA : konsekwencją takiego użycia kolekcji klucz-wartość jest to, że powinny one być niezmienne, ale nie zamierzam ujawniać interfejsu, aby umożliwić ich zmianę, więc kolekcje zmienne lub niezmienne są w porządku)
Problem polega na tym, że dykty w Pythonie nie mogą pojawiać się jako klucze do innych dykt. Nawet użycie krotki (tak jak bym to robił) nie pomaga.
>>> cache = {}
>>> rule = {"foo":"bar"}
>>> cache[(rule, "baz")] = "quux"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>>
Myślę, że muszą to być krotki aż do końca. Teraz standardowa biblioteka Pythona zapewnia w przybliżeniu to, czego potrzebuję, collections.namedtuple
ma zupełnie inną składnię, ale może być używana jako klucz. kontynuacja z powyższej sesji:
>>> from collections import namedtuple
>>> Rule = namedtuple("Rule",rule.keys())
>>> cache[(Rule(**rule), "baz")] = "quux"
>>> cache
{(Rule(foo='bar'), 'baz'): 'quux'}
Ok. Ale muszę stworzyć klasę dla każdej możliwej kombinacji kluczy w regule, której chciałbym użyć, co nie jest takie złe, ponieważ każda reguła analizy wie dokładnie, jakich parametrów używa, aby klasa mogła być zdefiniowana w tym samym czasie jako funkcja analizująca regułę.
Edycja: Dodatkowym problemem z namedtuple
s jest to, że są ściśle pozycyjne. Dwie krotki, które wyglądają na różne, mogą w rzeczywistości być takie same:
>>> 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: Jak uzyskać pliki, dict
które mogą być używane jako klucze do innych dict
?
Po zhakowaniu odpowiedzi, oto bardziej kompletne rozwiązanie, którego używam. Zauważ, że wymaga to trochę więcej pracy, aby wynikowe polecenia były niejasno niezmienne ze względów praktycznych. Oczywiście nadal dość łatwo jest zhakować to, dzwoniąc, dict.__setitem__(instance, key, value)
ale wszyscy jesteśmy tutaj dorośli.
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
Muszą być niezmienne, przynajmniej po uruchomieniu mieszania, więc dlaczego nie buforująkey
ihash
wartości jako atrybutyhashdict
obiektu? Zmodyfikowałem__key()
i__hash__()
przetestowałem, aby potwierdzić, że jest znacznie szybszy. SO nie zezwala na sformatowany kod w komentarzach, więc umieszczę link tutaj: sam.aiki.info/hashdict.py