Immutable dictionary, only use as a key for another dictionary

前端 未结 8 2303
情歌与酒
情歌与酒 2020-12-05 04:52

I had the need to implement a hashable dict so I could use a dictionary as a key for another dictionary.

A few months ago I used this implementation: Python hashable

相关标签:
8条回答
  • 2020-12-05 05:05

    If you are only using it as a key for another dict, you could go for frozenset(mutabledict.items()). If you need to access the underlying mappings, you could then use that as the parameter to dict.

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

    Note that you could also combine this with a class derived from dict, and use the frozenset as the source of the hash, while disabling __setitem__, as suggested in another answer. (@RaymondHettinger's answer for code which does just that).

    0 讨论(0)
  • 2020-12-05 05:10

    In order for your immutable dictionary to be safe, all it needs to do is never change its hash. Why don't you just disable __setitem__ as follows:

    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
    

    The output of the script is:

    {{'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
    
    0 讨论(0)
  • 2020-12-05 05:13

    Variation of Raymond Hettinger's answer by wrapping the self._dict with types.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)
    
    0 讨论(0)
  • 2020-12-05 05:20

    I realize this has already been answered, but types.MappingProxyType is an analogous implementation for Python 3.3. Regarding the original question of safety, there is a discussion in PEP 416 -- Add a frozendict builtin type on why the idea of a frozendict was rejected.

    0 讨论(0)
  • 2020-12-05 05:21

    Here is a link to pip install-able implementation of @RaymondHettinger's answer: https://github.com/pcattori/icicle

    Simply pip install icicle and you can from icicle import FrozenDict!

    Update: icicle has been deprecated in favor of maps: https://github.com/pcattori/maps (documentation, PyPI).

    0 讨论(0)
  • 2020-12-05 05:23

    It appears I am late to post. Not sure if anyone else has come up with ideas. But here is my take on it. The Dict is immutable and hashable. I made it immutable by overriding all the methods, magic and otherwise, with a custom '_readonly' function that raises an Exception. This is done when the object is instantiated. To get around the problem of not being able to apply the values I set the 'hash' under '__new__'. I then I override the '__hash__'function. Thats it!

    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
    

    Test:

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

    dict1 = {immutabled1: "Yay"}

    dict1[immutabled1]

    "Yay"

    dict1

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

    0 讨论(0)
提交回复
热议问题