1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import sys
21
22
23
24
26 """Implements a basic dictionary iterator with 3 modes.
27 If mode=0 (default) returns the keys (by returns I mean iterates over !)
28 If mode=1 returns the values
29 If mode=-1 returns the items - (key, value) tuple
30 mode=0 equates to the iterkeys method or __iter__ (for entry in dict)
31 mode=1 equates to the itervalues method.
32 mode=-1 equates to the iteritems method.
33 """
35 self.thedict = indict
36 self.inseq = indict.keys()
37 self.index = 0
38 self.mode = mode
39
41 if self.index >= len(self.inseq): raise StopIteration
42 thekey = self.inseq[self.index]
43 self.index += 1
44 if not self.mode:
45 return thekey
46 elif self.mode == 1:
47 return self.thedict[thekey]
48 elif self.mode == -1:
49 return (thekey, self.thedict[thekey])
50
53
54
55
57 """An ordered dictionary. ordereddict = oDict(indict, order)"""
58 __doc__ = """ordereddict = oDict({'a' : 1, 'b' : 2}, True)
59 The dictionary can be initialised with an optional dictionary passed in as the first argument,
60 You can also pass in an order parameter which chooses the sort method.
61 order=True (default) means all ordered methods use the normal sort function.
62 order=False means all ordered methods use the reverse sort function.
63 order=None means no sort function.
64
65 keys, items, iter and pop methods are ordered - based on the key.
66 The ordering is implemented in the keys() function.
67 The iterators are returned using the custom iterator dIter (which will work in three different ways)."""
68
69 - def __init__(self, indict={}, order=True):
70 self._thedict = {}
71 self._thedict.update(indict)
72 self._order = order
73
75 """Setting a keyword"""
76 self._thedict[item] = value
77
79 """Fetching a value."""
80 return self._thedict[item]
81
83 """Deleting a keyword"""
84 del self._thedict[item]
85
86 - def pop(self, item=[], default=None):
87 """Emulates the pop method.
88 If item is not supplied it pops the first value in the dictionary.
89 This is different from the normal dict pop method."""
90 if item != []:
91 return self._thedict.pop(item, default)
92 else:
93 try:
94 return self._thedict.pop(self.keys()[0])
95 except IndexError:
96 raise KeyError(': \'pop(): dictionary is empty\'')
97
99 """Emulates the popitem method - pops the first one in the list based on the chosen sort method."""
100 try:
101 theitem = self.keys()[0]
102 except IndexError:
103 raise KeyError(': \'popitem(): dictionary is empty\'')
104 return (theitem, self._thedict.pop(theitem))
105
107 """Does the dictionary have this key."""
108 return self._thedict.has_key(item)
109
111 """Does the dictionary have this key."""
112 return self._thedict.has_key(item)
113
115 """Fetch an item if it exists, otherwise set the item to default and return default."""
116 return self._thedict.setdefault(item, default)
117
118 - def get(self, item, default=None):
119 """Fetch the item if it exists, otherwise return default."""
120 return self._thedict.get(item, default)
121
123 """Update the current oDdict with the dictionary supplied."""
124 self._thedict.update(indict)
125
127 """Create a new oDict object that is a copy of this one."""
128 return oDict(self._thedict)
129
131 """Create a dictionary version of this oDict."""
132 return dict.copy(self._thedict)
133
135 """Clear oDict."""
136 self._thedict.clear()
137
139 """An oDict version of __repr__ """
140 return 'oDict(' + self._thedict.__repr__() + ')'
141
143 """Return an ordered list of the keys of this oDict."""
144 thelist = self._thedict.keys()
145 if self._order == True:
146 thelist.sort()
147 elif self._order == False:
148 thelist.sort()
149 thelist.reverse()
150 return thelist
151
153 """Like keys() but returns a list of (key, value)"""
154 return [(key, self._thedict[key]) for key in self.keys()]
155
157 """Like keys() but returns an ordered list of values (ordered by key)"""
158 return [self._thedict[key] for key in self.keys()]
159
161 """Return a new oDict initialised from the values supplied.
162 If sys.version_info > 2.2 this becomes a classmethod."""
163 return oDict(*args)
164
165 if (sys.version_info[0] + sys.version_info[1]/10.0) >= 2.2:
166 fromkeys = classmethod(fromkeys)
167
169 return len(self._thedict)
170
175
180
185
190
195
197 if hasattr(other, '_thedict'):
198 other = other._thedict
199 return self._thedict.__lt__(other)
200
202 if hasattr(other, '_thedict'):
203 other = other._thedict
204 return self._thedict.__le__(other)
205
207 """This just raises a TypeError."""
208 self._thedict.__hash__()
209
211 """Return an ordered iterator for the oDict."""
212 return dIter(self)
213
215 """Return an ordered iterator over the the oDict - returning (key, value) tuples."""
216 return dIter(self, -1)
217
219 """Return an ordered iterator over the keys the oDict."""
220 return dIter(self)
221
223 """Return an ordered iterator over the the values of the oDict - ordered by key."""
224 return dIter(self, 1)
225
227 """An oDict version of __str__ """
228 return 'oDict(' + self._thedict.__str__() + ')'
229
230
231
232 if __name__ == '__main__':
233 dictmethods = ['__class__', '__cmp__', '__contains__', '__delattr__', '__delitem__', '__doc__', '__eq__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__str__', 'clear', 'copy', 'fromkeys', 'get', 'has_key', 'items', 'iteritems', 'iterkeys', 'itervalues', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
234 odict = oDict({'x' : 'a', 'y' : 'b', 'z' : 'c'})
235 print 'print oDict.__doc__ \n', oDict.__doc__
236 print
237 print
238 print 'Attribute Test.\nTesting against the full attribute list for a normal dictionary. (40 attributes)'
239 for entry in dictmethods:
240 if not hasattr(odict, entry):
241 print 'oDict doesn\'t have attribute \'%s\'' % entry
242 print 'See the docs as to why those attributes are missing !!'
243 print 'Method test.\nIf nothing prints below this then all the tests passed !\n'
244 dlist = []
245 for key in odict.iterkeys(): dlist.append(key)
246 if dlist != ['x', 'y', 'z']: print 'Order fail in iterkeys method.'
247
248 dlist = []
249 for value in odict.itervalues(): dlist.append(value)
250 if dlist != ['a', 'b', 'c']: print 'Order fail in itervalues method.'
251
252 dlist = []
253 for item in odict.iteritems(): dlist.append(item)
254 if dlist != [('x','a'), ('y','b'), ('z','c')]: print 'Order fail in iteritems method.'
255
256 if not odict.keys() == ['x', 'y', 'z']: print 'Order fail in keys method.'
257 if not odict.values() == ['a', 'b', 'c']: print 'Order fail in values method.'
258 if not odict.items() == [('x','a'), ('y','b'), ('z','c')]: print 'Order fail in items method.'
259 dlist = []
260 while odict:
261 dlist.append(odict.pop())
262 if len(dlist) > 4:
263 print 'Fail in pop to remove items'
264 break
265 if dlist != ['a', 'b', 'c']: print 'Order fail in pop method.'
266 if not odict.fromkeys({'test':'z', 'fish':4}, False) == oDict({'test':'z', 'fish':4}, False): print 'Odd behaviour in fromkeys method.'
267
268
269 """
270 oDict is an ordered dictionary.
271 It behaves as a drop in replacement for an ordinary dictionary in almost every circumstance.
272 Many dictionary methods which normally return a random value, or return values in a randomn order.
273 Those methods in oDict return values in an ordered and consistent manner.
274 The ordering is applied in the keys() method and uses the Python sort() method of lists to do the sorting.
275 You can additionally set it to apply the reverse method by passing in a parameter when you create the instance.
276 See the oDict docstring for more details.
277
278 An ordered dictinary is useful where, for example, a consistent return order for the iterators and pop methods is helpful.
279 I use it in FSDM markup structures (describing files and directories in a file structure) so that the markup files are built in a consistent order.
280
281 Methods which are now ordered are :
282
283 pop, popitem, keys, items, values
284 iteritems, iterkeys, itervalues, __iter__ ( for key in odict )
285
286
287 As oDict has methods defined for almost all the dictionary methods, and also has custom iterators,
288 it would be a good template for anyone else who wanted to create a new dictionary type with custom access methods etc.
289
290 Doesn't subclass dict or use the iter function, so I think might be compatible with versions of Python pre 2.2 ?
291
292 Extra Methods, Not in a Normal dictionary :
293 'dict'
294
295 'pop' is slightly different to the normal dictionary method, it can be used without a parameter.
296 'str' and '__repr__' are modified to indicate this is an oDict rather than just a dictionary.
297
298 A lot of the methods that would return new dictionaries (copy, fromkeys) return new oDicts (hence the new dict method which returns an ordinary dictionary copy of the oDIct)
299
300 'Not Implemented Yet' Methods Include :
301 '__class__' : The default is fine.
302 '__getattribute__', '__setattr__', '__delattr__' : What the heck are these used for in a dict, probably raise errors. The standard 'classic' methods will be fine for us
303 '__new__' : not a new style class so don't need it
304 '__reduce__', '__reduce_ex__', : To do with pickling, I don't understand, hopefully Python can do something sensible anyway
305
306 The only time oDict won't act as a replacement for a dict is if the isinstance test is used.
307 In Python 2.2 you can fix this by making oDict a subclass of dict.
308
309 We could make oDict a bit more lightweight by overloading getattr.
310 Several methods that just call the same method on the underlying _thedict could all be replaced by one method that called getattr.
311 As one of the aims of oDict was to create a full dictionary like object, with all the methods defined, I won't do this. (__getitem__ and __setitem__ are two methods we could do away with).
312
313
314 TODO/ISSUES
315
316
317 CHANGELOG
318
319 05-07-04 Version 1.0.3
320 Fixed a bug in get.
321 Use some slightly more pythonic hasattr tests rather than isinstance.
322 Got rid of the slightly odd dum class.
323 Got rid of my suggested, nonsensical, __class__ stuff.
324 Changed the tests to use keys that would naturally be unordered !
325
326 20-06-04 Version 1.0.2
327 Slight change to the dum class, to give it a __cmp__ method.
328
329 17-06-04 Version 1.0.1
330 clear method is slightly better. (clears dictionary rather than rebinds)
331 Sorted out a few index errors where empty dictionaries are used. (raise KeyError rather than IndexError)
332 Made fromkeys a classmethod where Python Version > 2.2
333
334 16-06-04 Version 1.0.0
335 First version, appears to work fine.
336
337 """
338