Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: Freeze dict keys after creation [duplicate]

Is it possible to "freeze" a python dict after creation so that it's impossible to add new keys to it? It would only be possible to change existing key values.

If not, how do you know when you are changing an existing keyvalue pair, and when you are adding a new one?

like image 766
danihodovic Avatar asked Aug 11 '14 16:08

danihodovic


People also ask

Can a dictionary in Python have duplicate keys?

The straight answer is NO. You can not have duplicate keys in a dictionary in Python.

How do you make a dictionary immutable in Python?

I would just override the __setitem__() method of your dict. Note however, that this doesn't guarantee that the values of your dict will be immutable (say you have values that are lists, for example).

What is Frozendict Python?

frozendict is an immutable wrapper around dictionaries that implements the complete mapping interface. It can be used as a drop-in replacement for dictionaries where immutability is desired. Of course, this is python , and you can still poke around the object's internals if you want.

Can a dictionary key be a set?

The frozenset type is immutable and hashable — its contents cannot be altered after it is created; it can therefore be used as a dictionary key or as an element of another set.


2 Answers

Maybe something like this:

class FreezableDict (dict):
    __frozen = False

    def freeze (self):
        self.__frozen = True

    def __setitem__ (self, key, value):
        if self.__frozen and key not in self:
            raise ValueError('Dictionary is frozen')
        super().__setitem__(key, value)
>>> x = FreezableDict({'foo': 'bar', 'baz': 'bla'})
>>> x
{'baz': 'bla', 'foo': 'bar'}
>>> x['asdf'] = 'fdsa'
>>> x
{'asdf': 'fdsa', 'baz': 'bla', 'foo': 'bar'}
>>> x.freeze()
>>> x['hello'] = 'world'
Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    x['hello'] = 'world'
  File "<pyshell#13>", line 8, in __setitem__
    raise ValueError('Dictionary is frozen')
ValueError: Dictionary is frozen

Note that you might want to overwrite other methods too, including __delitem__, update, setdefault, pop, and popitem, as they can all modify the dictionary.


If you are interested in locking the dictionary completely, you could use types.MappingProxyType which provides a read-only view onto your dictionary. Once you have created your normal dictionary, you can then just create a mapping proxy of it which simply does not have any of the assignment/update functionality. You can also then get rid of any reference to the original dictionary (the mapping will keep one), to prevent it from being used to update it any further:

>>> x = {'foo': 'bar'}
>>> y = types.MappingProxyType(x)
>>> y
mappingproxy({'foo': 'bar'})
>>> x['baz'] = 'bla'
>>> y
mappingproxy({'baz': 'bla', 'foo': 'bar'})
>>> y['hello'] = 'world'
Traceback (most recent call last):
  File "<pyshell#55>", line 1, in <module>
    y['hello'] = 'world'
TypeError: 'mappingproxy' object does not support item assignment
>>> del x
>>> y
mappingproxy({'baz': 'bla', 'foo': 'bar'})

Or just in a single line, without ever having a reference to the original dictionary:

>>> x = types.MappingProxyType({'foo': 'bar', 'baz': 'bla'})
>>> x
mappingproxy({'baz': 'bla', 'foo': 'bar'})
>>> x['hello'] = 'world'
Traceback (most recent call last):
  File "<pyshell#60>", line 1, in <module>
    x['hello'] = 'world'
TypeError: 'mappingproxy' object does not support item assignment
like image 125
poke Avatar answered Sep 25 '22 15:09

poke


This isn't possible with a "vanilla" dict. You'll probably want to subclass collections.MutableMapping . . .

Untested code follows

class FrozenKeyDict(collections.MutableMapping):
    """Mapping which doesn't allow keys to be added/deleted.

    It does allow existing key/value pairs to be modified.
    """
    def __init__(self, *args, **kwargs):
        self._frozen = False
        self._dict = {}
        super(FrozenKeyDict, self).__init__(*args, **kwargs)
        self._frozen = True

    def __getitem__(self, key):
        return self._dict[key]

    def __setitem__(self, key, value):
        if self._frozen and key not in self._dict:
            raise KeyError('must be one of %s' % list(self))
        self._dict[key] = value

    def __delitem__(self, key):
        # modify to suit your needs ...
        raise KeyError('Removing keys not supported')

    def __iter__(self):
        return iter(self._dict)

    def __len__(self):
        return len(self._dict)
like image 21
mgilson Avatar answered Sep 25 '22 15:09

mgilson