4

I have a defaultdict(list) which I'd like to make immutable so that I can add objects of this type to a set. I have an idea as to how I can make it immutable, but it would require me write a few lines of code. Is there not a simpler way of doing this in python?

Isn't it common in python to populate, for example a defaultdict(list), while parsing a piece of data before freezing it once the parsing is complete?

Its also the case that an object can be of type tuple but can't be used as a dict key, for example:

>>> a = ([1,2],[2,3])
>>> type(a)
<type 'tuple'>
>>> d = dict()
>>> d[a] = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Why there exists a tuple of lists in python is also something I dont understand.

kylieCatt
  • 10,672
  • 5
  • 43
  • 51
Baz
  • 12,713
  • 38
  • 145
  • 268

2 Answers2

2

Another solution is to use pyrsistent package link. Pyrsistent is a number of persistent collections (by some referred to as functional data structures). Persistent in the sense that they are immutable.

I recommend using freeze link.

A minimal working example (MWE):

from pyrsistent import freeze
d = {"a":1, "b": 2}
d = freeze(d) # immutable dictionary
print(d)
## pmap({'b': 2, 'a': 1})

# Try to change a key-value
d["b"] = 10
## TypeError: 'PMap' object does not support item assignment

# If you want to change a value you need to create a new object using "update"
d.update({"b":10})
## pmap({'b': 10, 'a': 1})

Álvaro H.G
  • 397
  • 1
  • 10
1

To make a mutable object immutable, all its mutable containers must be replaced by their immutable counterparts. A dictionary where all values are immutable themselves can be made immutable trivially.

Adapting the example from the defaultdict(list)documentation:

import collections as coll
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d = coll.defaultdict(tuple)
for k, v in s:
    d[k] = d[k] + (v,)

print(d)
# prints
defaultdict(<class 'tuple'>, {'yellow': (1, 3), 'blue': (2, 4), 'red': (1,)})

The keys in our defaultdict(tuple) are immutable (strings), and values as well (tuple), as opposed to a defaultdict(list).

To freeze this dictionary:

def dict_freeze(d):
    # This is the trivial one-line function
    # It assumes the values are of immutable types, i.e. no lists.
    # It unpacks dict items, sorts and converts to tuple
    # sorting isn't strictly necessary, but dictionaries don't preserve order
    #   thus we could end up with the following:
    #   d = {'a': 1, 'b': 2} could get frozen as either of the following
    #   (('a', 1), ('b', 2)) != (('b', 2), ('a', 1))
    return tuple(sorted(d.items()))

frozen_d = dict_freeze(d)
print(frozen_d)
# prints
(('blue', (2, 4)), ('red', (1,)), ('yellow', (1, 3)))

Thus, I would recommend using defaultdict(tuple) instead of defaultdict(list) for this case and to freeze, just unpack, sort and convert to a tuple.

Haleemur Ali
  • 26,718
  • 5
  • 61
  • 85
  • "and values as well" Not really. Tuple's don't prevent their items to change. Try putting something mutable there. – muhuk May 12 '15 at 15:52
  • That is my point. read the start of the answer "To make a mutable object immutable, all its mutable containers must be replaced by their immutable counterparts". In this contrived example, the tuples in question contains only integers, so the "values in **this** defaultdict(tuple) are immutable" – Haleemur Ali May 12 '15 at 16:22