15

Short version: What's the best way to override dict.keys() and friends to keep myself from accidentally modifying my (supposedly) immutable dictionary in Python 3?

In a recent question I asked about Hashing an immutable dictionary in Python. Since then I have built an immutable, hashable dictionary I'm happy with. However, I realized it has a hole: the dictionary views returned by keys(), items(), and values() still allow myself accidentally to mutate my (supposedly) immutable dictionary.

The only question on Stack Overflow I could find about dictionary views was Python create own dict view of subset of dictionary, but that didn't seem to have much to do with my problem, and the answers to What would a "frozen dict" be? didn't seem to get into overriding keys(), etc.

Would doing something like this prevent me from accidentally modifying, for example, the keys of my immutable dictionary?

class FrozenCounter(collections.Counter):
    "Model an hashable multiset as an immutable dictionary."
    # ...
    def keys(self):
        return list(super().keys())
    def values(self):
        return list(super().values())
    def items(self):
        return list(super().items())


What I've gathered from the answers

I can't read, mainly.

dictviews cannot modify dicts. In the Python 3 documentation, I misread, "They provide a dynamic view on the dictionary’s entries, which means that when the dictionary changes, the view reflects these changes" as saying "when the view changes, the dictionary reflects these changes." Obviously that is not what the documentation said.

Alex Kulinkovich
  • 4,408
  • 15
  • 46
  • 50
wkschwartz
  • 3,817
  • 2
  • 29
  • 33

2 Answers2

6

In Python 2.x, views don't allow you to mutate your underlying object:

>>> a = { 'a' : 1 }
>>> a.keys()[0] = 'b'
>>> a
{'a': 1}
>>> a.values()[0] = 'b'
>>> a
{'a': 1}

In Python 3.x, mutating views gives a TypeError:

>>> a = { 'a':1}
>>> a.keys()[0] = 'b'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'dict_keys' object does not support item assignment
user1202136
  • 11,171
  • 4
  • 41
  • 62
  • @user1202136: `a.keys()[0]` is not a relevant test: as the error message indicates, it just says that you can set element #0 of `a.keys()`. It does not say that you are not allowed to modify it. The problem is that views *do not have an "element #0"* (they are mostly sets of objects with an arbitrary order). – Eric O. Lebigot Apr 29 '12 at 05:22
  • @EOL you are right: `a = { 'a' : set() }; print a; a.values()[0].add("0"); print a` – user1202136 Apr 30 '12 at 11:42
0

That's probably a bad idea, as it breaks the assumption that those methods return views in the first place, and replaces them with mutable objects that no longer update to match the underlying dict.

How are your views able to mutate your dictionary? Views don't support item assignment or deletion, so I don't believe that they can change the underlying dictionary.

Tim Lesher
  • 6,341
  • 2
  • 28
  • 42