В качестве упражнения и в основном для собственного развлечения я реализую анализатор packrat с возвратом. Вдохновением для этого является то, что я хотел бы получить лучшее представление о том, как гигиенические макросы будут работать на языке, подобном алгоритму (в отличие от диалектов лиспа без синтаксиса, в которых они обычно встречаются). Из-за этого разные проходы через входные данные могут видеть разные грамматики, поэтому кешированные результаты синтаксического анализа недействительны, если я также не сохраню текущую версию грамматики вместе с кешированными результатами синтаксического анализа. ( РЕДАКТИРОВАТЬ : следствием такого использования коллекций ключ-значение является то, что они должны быть неизменяемыми, но я не собираюсь открывать интерфейс, чтобы их можно было изменить, поэтому можно использовать изменяемые или неизменяемые коллекции)
Проблема в том, что диктовки Python не могут выступать в качестве ключей к другим диктовкам. Даже использование кортежа (как я бы все равно делал) не помогает.
>>> cache = {}
>>> rule = {"foo":"bar"}
>>> cache[(rule, "baz")] = "quux"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>>
Я думаю, это должны быть кортежи до самого конца. Теперь стандартная библиотека python предоставляет примерно то, что мне нужно, collections.namedtuple
имеет совершенно другой синтаксис, но может использоваться как ключ. продолжение с сеанса выше:
>>> from collections import namedtuple
>>> Rule = namedtuple("Rule",rule.keys())
>>> cache[(Rule(**rule), "baz")] = "quux"
>>> cache
{(Rule(foo='bar'), 'baz'): 'quux'}
ОК. Но мне нужно создать класс для каждой возможной комбинации ключей в правиле, которое я хотел бы использовать, что не так уж плохо, потому что каждое правило синтаксического анализа точно знает, какие параметры оно использует, поэтому этот класс может быть определен одновременно. как функция, разбирающая правило.
Изменить: дополнительная проблема с namedtuple
s заключается в том, что они строго позиционны. Два кортежа, которые кажутся разными, на самом деле могут быть одинаковыми:
>>> 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: Как мне получить dict
s, которые можно использовать как ключи к другим dict
s?
Немного поработав над ответами, вот более полное решение, которое я использую. Обратите внимание, что это делает небольшую дополнительную работу, чтобы сделать итоговые диктовки неопределенно неизменными для практических целей. Конечно, все еще довольно легко обойти это, позвонив, dict.__setitem__(instance, key, value)
но мы все здесь взрослые.
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
Должен быть неизменным, по крайней мере , после того, как вы начнете хэширования его, так почему бы не кэшироватьkey
иhash
значения в качестве атрибутовhashdict
объекта? Я модифицировал__key()
и__hash__()
протестировал, чтобы подтвердить, что это намного быстрее. SO не разрешает форматированный код в комментариях, поэтому я свяжу