如何使用一个字符串列表[其中可能包含任何字符]作为键?
如何使用一个字符串列表[其中可能包含任何字符]作为键?
基本上,我“理解”使用其他容器作为键的模糊性。-你是比较引用还是比较其中的值(以及深度是多少)。
那么,你能专门定义一个列表并创建自定义的比较/哈希操作符吗?我知道在我的应用程序中,我希望通过字符串的值(当然还有相对顺序)来比较字符串列表。
那么,我应该如何为这些类型的列表编写自定义哈希函数?或者换一种方式,我如何将列表转换为字符串,而不会引入分隔符的模糊性(该分隔符可能是字符串的一部分)!
关于这个问题:https://wiki.python.org/moin/DictionaryKeys
在那里直接说列表不能被用作字典的键;
也就是说,列表不能用作字典键的简单答案是,列表没有提供有效的哈希方法。当然,最明显的问题是,“为什么不能呢?”
因此,我写这篇文章来询问是否有办法使列表可哈希化;以及如何编写令人满意的哈希方法。
作为为什么我希望这样做的示例,请参见下面的代码:
namelist = sorted(["Jan", "Frank", "Kim"]) commonTrait = newTraits() traitdict = {} traitdict[namelist] = commonTrait; //稍后我希望检索它: trait = traitdict[sorted(["Jan", "Frank", "Kim"])]
在我直接使用的这个例子中,我考虑到列表的“排序”实际上并不重要(排序只是上述代码中进行的操作,以确保如果它们包含相同的内容,列表将始终相等)。
如何使用一个包含任何字符的字符串列表作为字典的键?
如果需要使用一个字符串的集合作为字典的键,有两个明显的选择:如果顺序重要,使用元组:
>>> d[tuple(['foo', 'bar'])] = 'baz' >>> d['foo', 'bar'] baz >>> d['bar', 'foo'] Traceback (most recent call last): File "", line 1, in KeyError: ('bar', 'foo')
如果顺序不重要,使用frozenset:
>>> d[frozenset(['foo', 'bar'])] = 'baz' >>> d[frozenset(['bar', 'foo'])] 'baz' >>> d[frozenset(['bar', 'foo', 'bar'])] 'baz'
如果计数重要但顺序不重要,使用排序后的元组:
>>> d[tuple(sorted(['foo', 'bar']))] = 'baz' >>> d[tuple(sorted(['bar', 'foo']))] 'baz' >>> d[tuple(sorted(['bar', 'foo', 'bar'])) Traceback (most recent call last): File "", line 1, in KeyError: ('bar', 'bar', 'foo')
与Perl的哈希表或JavaScript的对象属性不同,您不需要在Python中将字典键转换为字符串。
现在,关于可变列表不可哈希的问题:Python字典实现使用哈希表结构。它明确要求并假设键满足以下条件:
- 键实现了返回整数的__hash__方法
- 整数应该在输出范围内尽可能广泛分布,并均匀映射
- __hash__方法对于相同的对象返回一个不变的数值
- a == b意味着a.__hash__() == b.__hash__()
列表不能用作字典的键,因为:
>>> [].__hash__() Traceback (most recent call last): File "", line 1, in TypeError: 'NoneType' object is not callable
list类无法提供满足所有要求的__hash__方法,即a == b需要意味着a.__hash__() == b.__hash__()。
(它可以提供一个对每个列表返回0的实现,这样它将正常工作,但它将否定哈希的使用,因为所有列表都将映射到字典中的同一个槽,因为哈希代码将违反规则2)。
无法为列表创建__hash__方法:
>>> [].__hash__ = lambda x: 0 Traceback (most recent call last): File "", line 1, in AttributeError: 'list' object attribute '__hash__' is read-only
当然,我们总是可以看看如果列表具有__hash__方法会发生什么 - 我们创建一个list的子类并在其中提供__hash__方法;哈希代码的明显实现将是tuple()的哈希代码:
>>> class hashablelist(list): ... def __hash__(self): ... return hash(tuple(self)) ... >>> x = hashablelist(['a', 'b', 'c']) >>> y = hashablelist(['a', 'b', 'd']) >>> d = {} >>> d[x] = 'foo' >>> d[y] = 'bar' >>> d.items() [(['a', 'b', 'c'], 'foo'), (['a', 'b', 'd'], 'bar')] >>> y[2] = 'c' >>> d.items() [(['a', 'b', 'c'], 'foo'), (['a', 'b', 'c'], 'bar')] >>> del d[x] >>> del d[y] Traceback (most recent call last): File "", line 1, in KeyError: ['a', 'b', 'c'] >>> d.items() [(['a', 'b', 'c'], 'bar')] >>> x in d False >>> y in d False >>> x in d.keys() True >>> y in d.keys() True
这段代码显示,我们只是设法得到了一个损坏的字典 - 没有办法通过键直接访问或删除['a', 'b', 'c'] -> 'bar'的键值对,即使在.keys(),.values()和.items()中可见。