【发布时间】:2010-11-12 04:57:44
【问题描述】:
作为一个练习,主要是为了我自己的娱乐,我正在实现一个回溯 Packrat 解析器。这样做的灵感是我想更好地了解卫生宏如何在类似 algol 的语言中工作(与您通常在其中找到的无语法 lisp 方言相对)。正因为如此,不同的通过输入可能会看到不同的语法,因此缓存的解析结果是无效的,除非我还将语法的当前版本与缓存的解析结果一起存储。 (编辑:使用键值集合的结果是它们应该是不可变的,但我不打算公开接口以允许它们被更改,所以无论是可变集合还是不可变集合没问题)
问题是 python dicts 不能作为其他 dicts 的键出现。即使使用元组(反正我会这样做)也无济于事。
>>> 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'}
好的。但是我必须为我想使用的规则中的每个可能的键组合创建一个类,这还不错,因为每个解析规则都知道它使用什么参数,因此可以同时定义该类作为解析规则的函数。
编辑:namedtuples 的另一个问题是它们是严格定位的。看起来应该不同的两个元组实际上可以是相同的:
>>> 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:我如何获得可以用作其他dicts 的密钥的dicts?
对答案有所了解后,这是我正在使用的更完整的解决方案。请注意,这做了一些额外的工作,以使生成的 dicts 出于实际目的模糊地不可变。当然,通过调用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不允许在cmets中格式化代码,所以我将它链接在这里:sam.aiki.info/hashdict.py -
我不知道这是否太简单了,但是用
str()散列字典可能有用吗? IE。在您的示例中:cache[(str(rule), "baz")] = "quux"
标签: python