不可变字典,仅用作另一个字典的键

Daa*_*mer 26 python

我需要实现一个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)

  • 我喜欢你的回答,但它仍然不是一成不变的。仍然可以访问 `ImmutableDict({"a" : 1}).dict` 变量并更改它。是的,您可以通过`__dict` 隐藏它,但是您仍然可以通过使用`ImmutableDict({"a" : 1})._ImmutableDict__dict` 来访问它。因此它不是“真正的”不可变的 ;-) (2认同)

use*_*136 9

为了使您的不可变字典安全,它所需要做的就是永远不要改变它的哈希值.你为什么不禁__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)

  • 仍然不是 100% 不可变的,因为 `object.__setattr__` 可以绕过这个。`&gt;&gt;&gt; b = ImmutableDict() &gt;&gt;&gt; b.__hash__() 3527539 &gt;&gt;&gt; object.__setattr__(b, "items", {"bacon": "eggs"}.items) &gt;&gt;&gt; b.__hash__( ) 28501310` (2认同)

lys*_*ing 9

我意识到这已经得到了解答,但types.MappingProxyType是Python 3.3的类似实现.关于最初的安全问题,在PEP 416中有一个讨论- 添加一个关于为什么a的想法frozendict被拒绝的内置类型.


Ped*_*ori 5

这是@RaymondHettinger的答案的pip install -able实现的链接:https : //github.com/pcattori/icicle

简单pip install icicle,您可以from icicle import FrozenDict

更新: icicle不推荐使用mapshttps : //github.com/pcattori/maps文档PyPI)。