1

Here is the problem; I have an immutable dictionary with huge set of items. The key type and the value types contained within this dict are themselves immutable. I would like to be able to mutate this dict (adding/removing/replacing key-value pairs) without having to do a full copy of the dict.

I am imagining some wrapper class for the immutable dict which adheres to the dict contract, and defaults to the immutable dict for values that have not been updated. I see the post How to “perfectly” override a dict? which I plan to leverage to make this wrapper.

Before I embark on implementing this design I just wanted to ask- is this construct already provided by the language? Or how else can I achieve the desired effect? I am on the latest version of Python (3.7) so I can use all language features available. Thanks!

flakes
  • 21,558
  • 8
  • 41
  • 88

2 Answers2

3

Take a look at collections.ChainMap. It's a wrapper around multiple dictionaries: all writes go to the first dictionary, and lookups are searched in order of the maps. So I think you could just do something like:

modified_map = {}
mutable_map = collections.ChainMap(modified_map, huge_immutable_map)
almiki
  • 455
  • 2
  • 4
  • [Looks like it does!](https://docs.python.org/3/library/collections.html) "Lookups search the underlying mappings successively until a key is found. In contrast, writes, updates, and deletions only operate on the first mapping." Fantastic, thank you so much! – flakes Apr 26 '19 at 22:22
  • 1
    I guess the one thing it can't do is remove an item that is in the immutable dict, if you wanted to be able to do that. – almiki Apr 26 '19 at 22:25
  • nope, I want the immutable dict to remain immutable. I'm just trying to prevent a full copy to be able to do so. – flakes Apr 26 '19 at 22:26
0

Let's say you used a frozendict implementation like this one:

class frozendict(collections.Mapping):
    """
    An immutable wrapper around dictionaries that implements the complete :py:class:`collections.Mapping`
    interface. It can be used as a drop-in replacement for dictionaries where immutability is desired.
    """

    dict_cls = dict

    def __init__(self, *args, **kwargs):
        self._dict = self.dict_cls(*args, **kwargs)
        self._hash = None

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

    def __contains__(self, key):
        return key in self._dict

    def copy(self, **add_or_replace):
        return self.__class__(self, **add_or_replace)

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

    def __len__(self):
        return len(self._dict)

    def __repr__(self):
        return '<%s %r>' % (self.__class__.__name__, self._dict)

    def __hash__(self):
        if self._hash is None:
            h = 0
            for key, value in self._dict.items():
                h ^= hash((key, value))
            self._hash = h
        return self._hash

If you wanted to mutate it, you could just reach in and mutate self._dict:

d = frozendict({'a': 1, 'b': 2})
d['a'] = 3  # This fails
mutable_dict = d._dict
mutable_dict['a'] = 3  # This works
print(d['a'])

It's a little yucky reaching into a class's protected members, but I'd say that's okay because what you're trying to do is a little yucky. If you want a mutable dictionary (just a dict) then use one. If you never want to mutate it, use a frozendict implementation like the one above. A hyrid of mutable and immutable makes no sense. All frozendict does is it doesn't implement the mutation dunder methods (__setitem__, __delitem__, etc.). Under the hood a frozendict is represented by a regular, mutable dict.

The above approach is superior in my view to the one you linked. Composability (having frozendict have a _dict property) is much easier to reason about than inheritance (subclassing dict) in many cases.

Bailey Parker
  • 15,599
  • 5
  • 53
  • 91
  • 1
    Ah, I'm not sure I was clear in the original question. I don't wish to mutate the immutable backing dict. It's shared with other code. – flakes Apr 26 '19 at 22:19