ActiveState Code

Recipe 498072: Implementing an Immutable Dictionary


The implementation of a dictionary, whose items cannot be reset or deleted, nor new can be added.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#!/usr/bin/env python

"""A tuctionary, or tuct, is the combination of a tuple with
a dictionary. A tuct has named items, but they cannot be
deleted or rebound, nor new can be added.
"""

class tuct(object):
	"""The tuct class. An immutable dictionary.
	"""
	
	def __init__(self, dict=None, **kwds):
		self.__data = {}
		if dict is not None:
			self.__data.update(dict)
		if len(kwds):
			self.__data.update(kwds)
	
	del __init__
	
	def __repr__(self):
		return repr(self.__data)
	
	def __cmp__(self, dict):
		if isinstance(dict, tuct):
			return cmp(self.__data, dict.__data)
		else:
			return cmp(self.__data, dict)
	
	def __len__(self):
		return len(self.__data)
	
	def __getitem__(self, key):
		return self.__data[key]
	
	def copy(self):
		if self.__class__ is tuct:
			return tuct(self.__data.copy())
		import copy
		__data = self.__data
		try:
			self.__data = {}
			c = copy.copy(self)
		finally:
			self.__data = __data
		c.update(self)
		return c
	def keys(self):
		return self.__data.keys()
	
	def items(self):
		return self.__data.items()
		
	def iteritems(self):
		return self.__data.iteritems()
		
	def iterkeys(self):
		return self.__data.iterkeys()
	
	def itervalues(self):
		return self.__data.itervalues()
	
	def values(self):
		return self.__data.values()
		
	def has_key(self, key):
		return self.__data.has_key(key)
	
	def get(self, key, failobj=None):
		if not self.has_key(key):
			return failobj
		return self[key]
		
	def __contains__(self, key):
		return key in self.__data
	
	@classmethod
	def fromkeys(cls, iterable, value=None):
		d = cls()
		for key in iterable:
			d[key] = value
		return d

Discussion

We define our class, which does not have methods that can alter the object.

Comments

  1. 1. At 11:49 a.m. on 7 sep 2006, Nilton Volpato said:

    Almost worked :). One can still use the __init__ method, which works just like an update.

    >>> t = tuct(a=20,c=40,b=30)
    >>> t
    {'a': 20, 'c': 40, 'b': 30}
    >>> t.__init__(a=0,z=1000)
    >>> t
    {'a': 0, 'c': 40, 'b': 30, 'z': 1000}
    >>>
    

    -- Nilton

  2. 2. At 4:07 p.m. on 7 sep 2006, Aristotelis Mikropoulos (the author) said:

    Speed. Because, if we had derived from dict, the class would be slower.

  3. 3. At 4:08 p.m. on 7 sep 2006, Aristotelis Mikropoulos (the author) said:

    Recommendation. So, do you recommend me something, in order to prevent this?

  4. 4. At 1:45 a.m. on 8 sep 2006, vincent delft said:

    Why not inherit from dict.

    class tuct(dict)
       def __setitem__(self, key, val):
           pass
    

    If I'm not wrong this does the same. But as Nilton mentioned, the via the __init__ you still can update it ;-).

    PS: I'm on Python 2.4

  5. 5. At 12:25 p.m. on 8 sep 2006, Anonymous said:

    __hash__. isn't hashability the primary reason for immutability?

    class tuct(dict):
        def __setitem__(self, key, value): pass
        def __hash__(self):
            items = self.items()
            res = hash(items[0])
            for item in items[1:]:
                res ^= hash(item)
            return res
    
  6. 6. At 8:17 a.m. on 9 sep 2006, Roel Mathys said:

    __init__. something like this?

    def __init__( self , dict = None , **kwds ) :
        if hasattr(self,'data'):
            raise AttributeError, '__init__'
        self.data = {}
        if dict is not None:
            self.data.update( dict )
            if len( kwds ) :
                self.data.update( kwds )
    
        del self.__init__
    

    bye, rm

  7. 7. At 1:53 p.m. on 16 sep 2006, tomer filiba said:

    why? slower? it ought to be faster. you should really test that.

  8. 8. At 1:53 p.m. on 16 sep 2006, tomer filiba said:

    why? slower? it ought to be faster. you should really test that.

  9. 9. At 9:06 a.m. on 13 jun 2007, thanos vassilakis said:

    RE: __hash__.

    >>> class tuct(dict):
    ...    def __setitem__(self, key, value): pass
    ...     def __hash__(self):
    ...         items = self.items()
    ...         res = hash(items[0])
    ...         for item in items[1:]:
    ...             res ^= hash(item)
    ...         return res
    
    >>> d = tuct(test='test')
    >>> d
    {'test': 'test'}
    >>> d['test'] = 1
    >>> d
    >>> d.update(test=90)
    >>> d
    {'test': 90}
    >>> d.setdefault('moo', 1)
    >>> d
    {'test': 90, 'moo': 1}
    

    I think the envelope/letter idiom works well, especially when you need a immutable map for a class attribute or a default value.

Sign in to comment