我需要实现一个hashable dict,所以我可以使用字典作为另一个字典的键.
几个月前我使用了这个实现:Python hashable dicts
但是我收到一位同事的通知说"这不是真的不可变,因此不安全.你可以使用它,但它确实让我感觉像一个悲伤的熊猫'.
所以我开始四处寻找创造一个不可变的东西.我没有必要将'key-dict'与另一个'key-dict'进行比较.它唯一的用途是作为另一个字典的关键.
我想出了以下内容:
class HashableDict(dict):
"""Hashable dict that can be used as a key in other dictionaries"""
def __new__(self, *args, **kwargs):
# create a new local dict, that will be used by the HashableDictBase closure class
immutableDict = dict(*args, **kwargs)
class HashableDictBase(object):
"""Hashable dict that can be used as a key in other dictionaries. This is now immutable"""
def __key(self):
"""Return a tuple of the current keys"""
return tuple((k, immutableDict[k]) for k in sorted(immutableDict))
def __hash__(self):
"""Return a hash of __key"""
return hash(self.__key())
def __eq__(self, other):
"""Compare two __keys"""
return self.__key() == other.__key() # pylint: disable-msg=W0212
def __repr__(self):
"""@see: dict.__repr__"""
return immutableDict.__repr__()
def __str__(self):
"""@see: dict.__str__"""
return immutableDict.__str__()
def __setattr__(self, *args):
raise TypeError("can't modify immutable instance")
__delattr__ = __setattr__
return HashableDictBase()
Run Code Online (Sandbox Code Playgroud)
我使用以下来测试功能:
d = {"a" : 1}
a = HashableDict(d)
b = HashableDict({"b" : 2})
print a
d["b"] = 2
print a
c = HashableDict({"a" : 1})
test = {a : "value with a dict as key (key a)",
b : "value with a dict as key (key b)"}
print test[a]
print test[b]
print test[c]
Run Code Online (Sandbox Code Playgroud)
这使:
{'a':1}
{'a':1}
值,dict为键(键a)
值,dict为键(键b)
值,dict为键(键a)
作为输出
这是我可以使用的"最好的"不可变词典,满足我的要求吗?如果没有,什么是更好的解决方案?
Mar*_*cin 39
如果您只是将它作为另一个的关键dict,那么您可以选择frozenset(mutabledict.items()).如果需要访问底层映射,则可以将其用作参数dict.
mutabledict = dict(zip('abc', range(3)))
immutable = frozenset(mutabledict.items())
read_frozen = dict(immutable)
read_frozen['a'] # => 1
Run Code Online (Sandbox Code Playgroud)
请注意,您也可以将其与派生自的类结合dict使用,并将其frozenset用作哈希的源,同时禁用__setitem__,如另一个答案所示.(@ RaymondHettinger对代码的回答就是这样).
Ray*_*ger 22
该映射的抽象基类,使这很容易实现:
import collections
class ImmutableDict(collections.Mapping):
def __init__(self, somedict):
self._dict = dict(somedict) # make a copy
self._hash = None
def __getitem__(self, key):
return self._dict[key]
def __len__(self):
return len(self._dict)
def __iter__(self):
return iter(self._dict)
def __hash__(self):
if self._hash is None:
self._hash = hash(frozenset(self._dict.items()))
return self._hash
def __eq__(self, other):
return self._dict == other._dict
Run Code Online (Sandbox Code Playgroud)
为了使您的不可变字典安全,它所需要做的就是永远不要改变它的哈希值.你为什么不禁__setitem__用如下:
class ImmutableDict(dict):
def __setitem__(self, key, value):
raise Exception("Can't touch this")
def __hash__(self):
return hash(tuple(sorted(self.items())))
a = ImmutableDict({'a':1})
b = {a:1}
print b
print b[a]
a['a'] = 0
Run Code Online (Sandbox Code Playgroud)
脚本的输出是:
{{'a': 1}: 1}
1
Traceback (most recent call last):
File "ex.py", line 11, in <module>
a['a'] = 0
File "ex.py", line 3, in __setitem__
raise Exception("Can't touch this")
Exception: Can't touch this
Run Code Online (Sandbox Code Playgroud)
我意识到这已经得到了解答,但types.MappingProxyType是Python 3.3的类似实现.关于最初的安全问题,在PEP 416中有一个讨论- 添加一个关于为什么a的想法frozendict被拒绝的内置类型.
这是@RaymondHettinger的答案的pip install -able实现的链接:https : //github.com/pcattori/icicle
简单pip install icicle,您可以from icicle import FrozenDict!
更新: icicle不推荐使用maps:https : //github.com/pcattori/maps(文档,PyPI)。