Python 不可变字典,仅用作其他字典的键

Python 不可变字典,仅用作其他字典的键,python,Python,我需要实现一个hashable dict,这样我就可以使用一个字典作为另一个字典的键 几个月前,我使用了以下实现: 然而,我收到一位同事的通知,他说“这并不是一成不变的,因此也不安全。你可以用它,但它确实让我觉得自己像一只悲伤的熊猫 所以我开始四处寻找,创建一个不可变的。我没有必要将“关键dict”与另一个“关键dict”进行比较。它唯一的用途是作为另一本字典的键 我提出了以下建议: class HashableDict(dict): """Hashable dict that can

我需要实现一个hashable dict,这样我就可以使用一个字典作为另一个字典的键

几个月前,我使用了以下实现:

然而,我收到一位同事的通知,他说“这并不是一成不变的,因此也不安全。你可以用它,但它确实让我觉得自己像一只悲伤的熊猫

所以我开始四处寻找,创建一个不可变的。我没有必要将“关键dict”与另一个“关键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()
我使用以下内容来测试功能:

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]
其中:

{'a':1}
{'a':1}
以dict作为键的值(键a)
以dict作为键的值(键b)
以dict作为键的值(键a)

作为输出


这是我能使用的满足我需求的“最好的”不变字典吗?如果不是,有什么更好的解决方案吗?

如果您只是将它用作另一个
dict
的键,您可以选择
冻结集(mutabledict.items())
。如果需要访问底层映射,则可以将其用作
dict
的参数

mutabledict = dict(zip('abc', range(3)))
immutable = frozenset(mutabledict.items())
read_frozen = dict(immutable)
read_frozen['a'] # => 1

请注意,您还可以将其与从
dict
派生的类组合,并使用
冻结集
作为散列的源,同时禁用
\uuuuuuuu setitem\uuuuuu
,如另一个答案所示。(对于这样做的代码)。

为了确保不可变字典的安全,它所需要做的就是永远不要更改其哈希。为什么不按如下方式禁用
\uuuuu setitem\uuuuu

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
脚本的输出为:

{{'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
{{'a':1}:1}
1.
回溯(最近一次呼叫最后一次):
文件“ex.py”,第11行,在
a['a']=0
文件“ex.py”,第3行,在集合项中__
引发异常(“无法触摸此”)
例外:我不能碰这个
映射抽象基类使此功能易于实现:

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

我意识到这已经得到了回答,但这是Python3.3的类似实现。关于最初的安全问题,在中有一个讨论,讨论了为什么
冷冻dict
的想法被拒绝。

看来我发帖晚了。不确定是否有其他人想出了主意。但这是我对它的看法。Dict是不可变的和可散列的。我通过使用引发异常的自定义'\u readonly'函数重写所有方法(magic和其他方法),使其不可变。这是在实例化对象时完成的。为了解决无法应用这些值的问题,我在“\uuuu new\uuuuuuu”下设置了“hash”。然后重写“\uuuu hash\uuuuu”函数。就这样

class ImmutableDict(dict):

_HASH = None

def __new__(cls, *args, **kwargs):
    ImmutableDict._HASH = hash(frozenset(args[0].items()))
    return super(ImmutableDict, cls).__new__(cls, args)

def __hash__(self):
    return self._HASH

def _readonly(self, *args, **kwards):
    raise TypeError("Cannot modify Immutable Instance")

__delattr__ = __setattr__ = __setitem__ = pop = update = setdefault = clear = popitem = _readonly
测试:

immutabled1=ImmutableDict({“This”:“That”,“Cheese”:“Blarg”})

dict1={immutabled1:“耶”}

dict1[不可修改的1]

“耶”

格言1

{{'Cheese':'Blarg','This':'That'}:'Yay'}


以下是指向pip安装的链接-可实现:

只需
pip安装冰柱
,您就可以
从冰柱导入FrozenDict

更新:
冰柱
已被弃用,取而代之的是
地图
:(,)。

通过使用
类型来包装
自身。
。MappingProxyType

class ImmutableDict(collections.Mapping):
    """
    Copies a dict and proxies it via types.MappingProxyType to make it immutable.
    """
    def __init__(self, somedict):
        dictcopy = dict(somedict) # make a copy
        self._dict = MappingProxyType(dictcopy) # lock it
        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

    def __repr__(self):
        return str(self._dict)
您可以使用枚举:

import enum

KeyDict1 = enum.Enum('KeyDict1', {'InnerDictKey1':'bla', 'InnerDictKey2 ':2})

d = { KeyDict1: 'whatever', KeyDict2: 1, ...}
您可以像访问字典一样访问枚举:

KeyDict1['InnerDictKey2'].value  # This is 2

您可以迭代这些名称,并获取它们的值。。。它完成了您所期望的一切。

稍微好一点的方法是
tuple(排序(immutableDict.items())
(或
iteritems()
pre 3.x)。另外,请注意,我将使用
FrozenDict
作为Python中默认存在的
frozenset
类的名称,只是为了命名的一致性——这并不重要。您的同事可能忽略了“同意的成人语言”的要点,即纯Python代码中没有真正私有的东西你的代码所做的非常接近于创建不可变对象的意图。考虑Guido van Rossum、Alex Martelli、Greg Wilson和我自己编写的Lib /SETS.Py中的iMunababelEt代码。标准库代码中核心开发者的代码是否会让你的同事“感觉像一只悲伤的熊猫”??我喜欢这样-一个
dict
本质上是无序的,所以排序然后使它成为一个元组似乎是一种通过强制排序来确保平等的黑客方法-如果你存储的东西有奇怪的排序,这可能会破坏。这种方法不会这样做。这种方法更简单、更干净,我认为最好。正如我在另一个答案中所说的,不可用,而不是pythonic.
enum
或更好。@MarcoSulla Python 3.4于2014年3月16日发布。这是使用enum的第一个版本。这个答案是从2012年开始的。
frozendict
是在这个答案后大约6个月发布的,仍然不是100%不变的,因为
对象。uu setattr\uuu
可以绕过这个问题。
>b=ImmutableDict()我喜欢你的答案,但它仍然不是不可变的。你仍然可以到达不可变的目录({“a”:1}).dict
变量并对其进行更改。是的,您可以通过
\uu dict
将其隐藏,但是您仍然可以使用
不可变dict({“a”:1})来访问它。不可变dict\uuu dict
。因此它不是“真的”不可变;-)您也缺少了
\uueq\ucode>方法。它也在使用那个。当您随后更改
.dict
时,self.hash将不会被更新,看起来它仍然会使用self.hash,但它似乎不会使用self.hash来比较它们的键。它还使用
\uuuuu eq\uuuu
。当我重写它并比较
\uuuuuu散列\uuuuuuu
方法时,它确实起了作用。我已经实现了@RaymondHettinger的解决方案,并对其进行了打包,以便它能够
pip安装。查看更多详细信息。如果要对colle进行排序