1 # -*- coding: utf-8 -*-
3 # An ordered dictionary implementation. OrderedDict entered the
4 # Python Standard Library in Python 2.7 and 3.1. See `PEP 372`_ for
5 # details. Code_ by Armin Ronacher and the PEP 273 authors.
7 # .. _PEP 372: http://www.python.org/dev/peps/pep-0372/
8 # .. _Code: http://dev.pocoo.org/hg/sandbox/raw-file/tip/odict.py
14 This module is an example implementation of an ordered dict for the
15 collections module. It's not written for performance (it actually
16 performs pretty bad) but to show how the API works.
22 Why would anyone need ordered dicts?
24 Dicts in python are unordered which means that the order of items when
25 iterating over dicts is undefined. As a matter of fact it is most of
26 the time useless and differs from implementation to implementation.
28 Many developers stumble upon that problem sooner or later when
29 comparing the output of doctests which often does not match the order
30 the developer thought it would.
32 Also XML systems such as Genshi have their problems with unordered
33 dicts as the input and output ordering of tag attributes is often
34 mixed up because the ordering is lost when converting the data into
35 a dict. Switching to lists is often not possible because the
36 complexity of a lookup is too high.
38 Another very common case is metaprogramming. The default namespace
39 of a class in python is a dict. With Python 3 it becomes possible
40 to replace it with a different object which could be an ordered dict.
41 Django is already doing something similar with a hack that assigns
42 numbers to some descriptors initialized in the class body of a
43 specific subclass to restore the ordering after class creation.
45 When porting code from programming languages such as PHP and Ruby
46 where the item-order in a dict is guaranteed it's also a great help
47 to have an equivalent data structure in Python to ease the transition.
49 Where are new keys added?
51 At the end. This behavior is consistent with Ruby 1.9 Hashmaps
52 and PHP Arrays. It also matches what common ordered dict
53 implementations do currently.
55 What happens if an existing key is reassigned?
57 The key is *not* moved. This is consitent with existing
58 implementations and can be changed by a subclass very easily::
60 class movingodict(odict):
61 def __setitem__(self, key, value):
63 odict.__setitem__(self, key, value)
65 Moving keys to the end of a ordered dict on reassignment is not
66 very useful for most applications.
68 Does it mean the dict keys are sorted by a sort expression?
70 That's not the case. The odict only guarantees that there is an order
71 and that newly inserted keys are inserted at the end of the dict. If
72 you want to sort it you can do so, but newly added keys are again added
73 at the end of the dict.
75 I initializes the odict with a dict literal but the keys are not
76 ordered like they should!
78 Dict literals in Python generate dict objects and as such the order of
79 their items is not guaranteed. Before they are passed to the odict
80 constructor they are already unordered.
82 What happens if keys appear multiple times in the list passed to the
85 The same as for the dict. The latter item overrides the former. This
86 has the side-effect that the position of the first key is used because
87 the key is actually overwritten:
89 >>> odict([('a', 1), ('b', 2), ('a', 3)])
90 odict.odict([('a', 3), ('b', 2)])
92 This behavor is consistent with existing implementation in Python
93 and the PHP array and the hashmap in Ruby 1.9.
95 This odict doesn't scale!
97 Yes it doesn't. The delitem operation is O(n). This is file is a
98 mockup of a real odict that could be implemented for collections
99 based on an linked list.
101 Why is there no .insert()?
103 There are few situations where you really want to insert a key at
104 an specified index. To now make the API too complex the proposed
105 solution for this situation is creating a list of items, manipulating
106 that and converting it back into an odict:
108 >>> d = odict([('a', 42), ('b', 23), ('c', 19)])
110 >>> l.insert(1, ('x', 0))
112 odict.odict([('a', 42), ('x', 0), ('b', 23), ('c', 19)])
114 :copyright: (c) 2008 by Armin Ronacher and PEP 273 authors.
115 :license: modified BSD license.
117 from itertools import izip, imap
118 from copy import deepcopy
125 Ordered dict example implementation.
127 This is the proposed interface for a an ordered dict as proposed on the
128 Python mailinglist (proposal_).
130 It's a dict subclass and provides some list functions. The implementation
131 of this class is inspired by the implementation of Babel but incorporates
132 some ideas from the `ordereddict`_ and Django's ordered dict.
134 The constructor and `update()` both accept iterables of tuples as well as
137 >>> d = odict([('a', 'b'), ('c', 'd')])
138 >>> d.update({'foo': 'bar'})
140 odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')])
142 Keep in mind that when updating from dict-literals the order is not
143 preserved as these dicts are unsorted!
145 You can copy an odict like a dict by using the constructor, `copy.copy`
146 or the `copy` method and make deep copies with `copy.deepcopy`:
148 >>> from copy import copy, deepcopy
150 odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')])
152 odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')])
154 odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')])
157 >>> d2['spam'].append('eggs')
159 odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])])
161 odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', ['eggs'])])
163 All iteration methods as well as `keys`, `values` and `items` return
164 the values ordered by the the time the key-value pair is inserted:
167 ['a', 'c', 'foo', 'spam']
169 ['b', 'd', 'bar', []]
171 [('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])]
172 >>> list(d.iterkeys())
173 ['a', 'c', 'foo', 'spam']
174 >>> list(d.itervalues())
175 ['b', 'd', 'bar', []]
176 >>> list(d.iteritems())
177 [('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])]
179 Index based lookup is supported too by `byindex` which returns the
180 key/value pair for an index:
185 You can reverse the odict as well:
189 odict.odict([('spam', []), ('foo', 'bar'), ('c', 'd'), ('a', 'b')])
191 And sort it like a list:
193 >>> d.sort(key=lambda x: x[0].lower())
195 odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])])
197 .. _proposal: http://thread.gmane.org/gmane.comp.python.devel/95316
198 .. _ordereddict: http://www.xs4all.nl/~anthon/Python/ordereddict/
201 def __init__(self, *args, **kwargs):
204 self.update(*args, **kwargs)
206 def __delitem__(self, key):
207 dict.__delitem__(self, key)
208 self._keys.remove(key)
210 def __setitem__(self, key, item):
211 if not hasattr(self, '_keys'):
212 self._keys = [] # work around multiprocessing.Queue rebuild
214 self._keys.append(key)
215 dict.__setitem__(self, key, item)
217 def __deepcopy__(self, memo=None):
220 d = memo.get(id(self), missing)
223 memo[id(self)] = d = self.__class__()
224 dict.__init__(d, deepcopy(self.items(), memo))
225 d._keys = self._keys[:]
228 def __getstate__(self):
229 return {'items': dict(self), 'keys': self._keys}
231 def __setstate__(self, d):
232 self._keys = d['keys']
233 dict.update(d['items'])
235 def __reversed__(self):
236 return reversed(self._keys)
238 def __eq__(self, other):
239 if isinstance(other, odict):
240 if not dict.__eq__(self, other):
242 return self.items() == other.items()
243 return dict.__eq__(self, other)
245 def __ne__(self, other):
246 return not self.__eq__(other)
248 def __cmp__(self, other):
249 if isinstance(other, odict):
250 return cmp(self.items(), other.items())
251 elif isinstance(other, dict):
252 return dict.__cmp__(self, other)
253 return NotImplemented
256 def fromkeys(cls, iterable, default=None):
257 return cls((key, default) for key in iterable)
264 return self.__class__(self)
267 return zip(self._keys, self.values())
270 return izip(self._keys, self.itervalues())
276 return iter(self._keys)
278 def pop(self, key, default=missing):
279 if default is missing:
280 return dict.pop(self, key)
281 elif key not in self:
283 self._keys.remove(key)
284 return dict.pop(self, key, default)
286 def popitem(self, key):
287 self._keys.remove(key)
288 return dict.popitem(key)
290 def setdefault(self, key, default=None):
292 self._keys.append(key)
293 dict.setdefault(self, key, default)
295 def update(self, *args, **kwargs):
298 if hasattr(args[0], 'iteritems'):
299 sources.append(args[0].iteritems())
301 sources.append(iter(args[0]))
303 raise TypeError('expected at most one positional argument')
305 sources.append(kwargs.iteritems())
306 for iterable in sources:
307 for key, val in iterable:
311 return map(self.get, self._keys)
313 def itervalues(self):
314 return imap(self.get, self._keys)
316 def index(self, item):
317 return self._keys.index(item)
319 def byindex(self, item):
320 key = self._keys[item]
321 return (key, dict.__getitem__(self, key))
326 def sort(self, *args, **kwargs):
327 self._keys.sort(*args, **kwargs)
330 return 'odict.odict(%r)' % self.items()
336 if __name__ == '__main__':