]> git.openstreetmap.org Git - osqa.git/blob - forum/utils/odict.py
initial import
[osqa.git] / forum / utils / odict.py
1 # odict.py
2 # An Ordered Dictionary object
3 # Copyright (C) 2005 Nicola Larosa, Michael Foord
4 # E-mail: nico AT tekNico DOT net, fuzzyman AT voidspace DOT org DOT uk
5
6 # This software is licensed under the terms of the BSD license.
7 # http://www.voidspace.org.uk/python/license.shtml
8 # Basically you're free to copy, modify, distribute and relicense it,
9 # So long as you keep a copy of the license with it.
10
11 # Documentation at http://www.voidspace.org.uk/python/odict.html
12 # For information about bugfixes, updates and support, please join the
13 # Pythonutils mailing list:
14 # http://groups.google.com/group/pythonutils/
15 # Comments, suggestions and bug reports welcome.
16
17 """A dict that keeps keys in insertion order"""
18 from __future__ import generators
19
20 __author__ = ('Nicola Larosa <nico-NoSp@m-tekNico.net>,'
21     'Michael Foord <fuzzyman AT voidspace DOT org DOT uk>')
22
23 __docformat__ = "restructuredtext en"
24
25 __revision__ = '$Id: odict.py 129 2005-09-12 18:15:28Z teknico $'
26
27 __version__ = '0.2.2'
28
29 __all__ = ['OrderedDict', 'SequenceOrderedDict']
30
31 import sys
32 INTP_VER = sys.version_info[:2]
33 if INTP_VER < (2, 2):
34     raise RuntimeError("Python v.2.2 or later required")
35
36 import types, warnings
37
38 class OrderedDict(dict):
39     """
40     A class of dictionary that keeps the insertion order of keys.
41     
42     All appropriate methods return keys, items, or values in an ordered way.
43     
44     All normal dictionary methods are available. Update and comparison is
45     restricted to other OrderedDict objects.
46     
47     Various sequence methods are available, including the ability to explicitly
48     mutate the key ordering.
49     
50     __contains__ tests:
51     
52     >>> d = OrderedDict(((1, 3),))
53     >>> 1 in d
54     1
55     >>> 4 in d
56     0
57     
58     __getitem__ tests:
59     
60     >>> OrderedDict(((1, 3), (3, 2), (2, 1)))[2]
61     1
62     >>> OrderedDict(((1, 3), (3, 2), (2, 1)))[4]
63     Traceback (most recent call last):
64     KeyError: 4
65     
66     __len__ tests:
67     
68     >>> len(OrderedDict())
69     0
70     >>> len(OrderedDict(((1, 3), (3, 2), (2, 1))))
71     3
72     
73     get tests:
74     
75     >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
76     >>> d.get(1)
77     3
78     >>> d.get(4) is None
79     1
80     >>> d.get(4, 5)
81     5
82     >>> d
83     OrderedDict([(1, 3), (3, 2), (2, 1)])
84     
85     has_key tests:
86     
87     >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
88     >>> d.has_key(1)
89     1
90     >>> d.has_key(4)
91     0
92     """
93
94     def __init__(self, init_val=(), strict=False):
95         """
96         Create a new ordered dictionary. Cannot init from a normal dict,
97         nor from kwargs, since items order is undefined in those cases.
98         
99         If the ``strict`` keyword argument is ``True`` (``False`` is the
100         default) then when doing slice assignment - the ``OrderedDict`` you are
101         assigning from *must not* contain any keys in the remaining dict.
102         
103         >>> OrderedDict()
104         OrderedDict([])
105         >>> OrderedDict({1: 1})
106         Traceback (most recent call last):
107         TypeError: undefined order, cannot get items from dict
108         >>> OrderedDict({1: 1}.items())
109         OrderedDict([(1, 1)])
110         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
111         >>> d
112         OrderedDict([(1, 3), (3, 2), (2, 1)])
113         >>> OrderedDict(d)
114         OrderedDict([(1, 3), (3, 2), (2, 1)])
115         """
116         self.strict = strict
117         dict.__init__(self)
118         if isinstance(init_val, OrderedDict):
119             self._sequence = init_val.keys()
120             dict.update(self, init_val)
121         elif isinstance(init_val, dict):
122             # we lose compatibility with other ordered dict types this way
123             raise TypeError('undefined order, cannot get items from dict')
124         else:
125             self._sequence = []
126             self.update(init_val)
127
128 ### Special methods ###
129
130     def __delitem__(self, key):
131         """
132         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
133         >>> del d[3]
134         >>> d
135         OrderedDict([(1, 3), (2, 1)])
136         >>> del d[3]
137         Traceback (most recent call last):
138         KeyError: 3
139         >>> d[3] = 2
140         >>> d
141         OrderedDict([(1, 3), (2, 1), (3, 2)])
142         >>> del d[0:1]
143         >>> d
144         OrderedDict([(2, 1), (3, 2)])
145         """
146         if isinstance(key, types.SliceType):
147             # FIXME: efficiency?
148             keys = self._sequence[key]
149             for entry in keys:
150                 dict.__delitem__(self, entry)
151             del self._sequence[key]
152         else:
153             # do the dict.__delitem__ *first* as it raises
154             # the more appropriate error
155             dict.__delitem__(self, key)
156             self._sequence.remove(key)
157
158     def __eq__(self, other):
159         """
160         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
161         >>> d == OrderedDict(d)
162         True
163         >>> d == OrderedDict(((1, 3), (2, 1), (3, 2)))
164         False
165         >>> d == OrderedDict(((1, 0), (3, 2), (2, 1)))
166         False
167         >>> d == OrderedDict(((0, 3), (3, 2), (2, 1)))
168         False
169         >>> d == dict(d)
170         False
171         >>> d == False
172         False
173         """
174         if isinstance(other, OrderedDict):
175             # FIXME: efficiency?
176             #   Generate both item lists for each compare
177             return (self.items() == other.items())
178         else:
179             return False
180
181     def __lt__(self, other):
182         """
183         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
184         >>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
185         >>> c < d
186         True
187         >>> d < c
188         False
189         >>> d < dict(c)
190         Traceback (most recent call last):
191         TypeError: Can only compare with other OrderedDicts
192         """
193         if not isinstance(other, OrderedDict):
194             raise TypeError('Can only compare with other OrderedDicts')
195         # FIXME: efficiency?
196         #   Generate both item lists for each compare
197         return (self.items() < other.items())
198
199     def __le__(self, other):
200         """
201         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
202         >>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
203         >>> e = OrderedDict(d)
204         >>> c <= d
205         True
206         >>> d <= c
207         False
208         >>> d <= dict(c)
209         Traceback (most recent call last):
210         TypeError: Can only compare with other OrderedDicts
211         >>> d <= e
212         True
213         """
214         if not isinstance(other, OrderedDict):
215             raise TypeError('Can only compare with other OrderedDicts')
216         # FIXME: efficiency?
217         #   Generate both item lists for each compare
218         return (self.items() <= other.items())
219
220     def __ne__(self, other):
221         """
222         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
223         >>> d != OrderedDict(d)
224         False
225         >>> d != OrderedDict(((1, 3), (2, 1), (3, 2)))
226         True
227         >>> d != OrderedDict(((1, 0), (3, 2), (2, 1)))
228         True
229         >>> d == OrderedDict(((0, 3), (3, 2), (2, 1)))
230         False
231         >>> d != dict(d)
232         True
233         >>> d != False
234         True
235         """
236         if isinstance(other, OrderedDict):
237             # FIXME: efficiency?
238             #   Generate both item lists for each compare
239             return not (self.items() == other.items())
240         else:
241             return True
242
243     def __gt__(self, other):
244         """
245         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
246         >>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
247         >>> d > c
248         True
249         >>> c > d
250         False
251         >>> d > dict(c)
252         Traceback (most recent call last):
253         TypeError: Can only compare with other OrderedDicts
254         """
255         if not isinstance(other, OrderedDict):
256             raise TypeError('Can only compare with other OrderedDicts')
257         # FIXME: efficiency?
258         #   Generate both item lists for each compare
259         return (self.items() > other.items())
260
261     def __ge__(self, other):
262         """
263         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
264         >>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
265         >>> e = OrderedDict(d)
266         >>> c >= d
267         False
268         >>> d >= c
269         True
270         >>> d >= dict(c)
271         Traceback (most recent call last):
272         TypeError: Can only compare with other OrderedDicts
273         >>> e >= d
274         True
275         """
276         if not isinstance(other, OrderedDict):
277             raise TypeError('Can only compare with other OrderedDicts')
278         # FIXME: efficiency?
279         #   Generate both item lists for each compare
280         return (self.items() >= other.items())
281
282     def __repr__(self):
283         """
284         Used for __repr__ and __str__
285         
286         >>> r1 = repr(OrderedDict((('a', 'b'), ('c', 'd'), ('e', 'f'))))
287         >>> r1
288         "OrderedDict([('a', 'b'), ('c', 'd'), ('e', 'f')])"
289         >>> r2 = repr(OrderedDict((('a', 'b'), ('e', 'f'), ('c', 'd'))))
290         >>> r2
291         "OrderedDict([('a', 'b'), ('e', 'f'), ('c', 'd')])"
292         >>> r1 == str(OrderedDict((('a', 'b'), ('c', 'd'), ('e', 'f'))))
293         True
294         >>> r2 == str(OrderedDict((('a', 'b'), ('e', 'f'), ('c', 'd'))))
295         True
296         """
297         return '%s([%s])' % (self.__class__.__name__, ', '.join(
298             ['(%r, %r)' % (key, self[key]) for key in self._sequence]))
299
300     def __setitem__(self, key, val):
301         """
302         Allows slice assignment, so long as the slice is an OrderedDict
303         >>> d = OrderedDict()
304         >>> d['a'] = 'b'
305         >>> d['b'] = 'a'
306         >>> d[3] = 12
307         >>> d
308         OrderedDict([('a', 'b'), ('b', 'a'), (3, 12)])
309         >>> d[:] = OrderedDict(((1, 2), (2, 3), (3, 4)))
310         >>> d
311         OrderedDict([(1, 2), (2, 3), (3, 4)])
312         >>> d[::2] = OrderedDict(((7, 8), (9, 10)))
313         >>> d
314         OrderedDict([(7, 8), (2, 3), (9, 10)])
315         >>> d = OrderedDict(((0, 1), (1, 2), (2, 3), (3, 4)))
316         >>> d[1:3] = OrderedDict(((1, 2), (5, 6), (7, 8)))
317         >>> d
318         OrderedDict([(0, 1), (1, 2), (5, 6), (7, 8), (3, 4)])
319         >>> d = OrderedDict(((0, 1), (1, 2), (2, 3), (3, 4)), strict=True)
320         >>> d[1:3] = OrderedDict(((1, 2), (5, 6), (7, 8)))
321         >>> d
322         OrderedDict([(0, 1), (1, 2), (5, 6), (7, 8), (3, 4)])
323         
324         >>> a = OrderedDict(((0, 1), (1, 2), (2, 3)), strict=True)
325         >>> a[3] = 4
326         >>> a
327         OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
328         >>> a[::1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
329         >>> a
330         OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
331         >>> a[:2] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)])
332         Traceback (most recent call last):
333         ValueError: slice assignment must be from unique keys
334         >>> a = OrderedDict(((0, 1), (1, 2), (2, 3)))
335         >>> a[3] = 4
336         >>> a
337         OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
338         >>> a[::1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
339         >>> a
340         OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
341         >>> a[:2] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
342         >>> a
343         OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
344         >>> a[::-1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
345         >>> a
346         OrderedDict([(3, 4), (2, 3), (1, 2), (0, 1)])
347         
348         >>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
349         >>> d[:1] = 3
350         Traceback (most recent call last):
351         TypeError: slice assignment requires an OrderedDict
352         
353         >>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
354         >>> d[:1] = OrderedDict([(9, 8)])
355         >>> d
356         OrderedDict([(9, 8), (1, 2), (2, 3), (3, 4)])
357         """
358         if isinstance(key, types.SliceType):
359             if not isinstance(val, OrderedDict):
360                 # FIXME: allow a list of tuples?
361                 raise TypeError('slice assignment requires an OrderedDict')
362             keys = self._sequence[key]
363             # NOTE: Could use ``range(*key.indices(len(self._sequence)))``
364             indexes = range(len(self._sequence))[key]
365             if key.step is None:
366                 # NOTE: new slice may not be the same size as the one being
367                 #   overwritten !
368                 # NOTE: What is the algorithm for an impossible slice?
369                 #   e.g. d[5:3]
370                 pos = key.start or 0
371                 del self[key]
372                 newkeys = val.keys()
373                 for k in newkeys:
374                     if k in self:
375                         if self.strict:
376                             raise ValueError('slice assignment must be from '
377                                 'unique keys')
378                         else:
379                             # NOTE: This removes duplicate keys *first*
380                             #   so start position might have changed?
381                             del self[k]
382                 self._sequence = (self._sequence[:pos] + newkeys +
383                     self._sequence[pos:])
384                 dict.update(self, val)
385             else:
386                 # extended slice - length of new slice must be the same
387                 # as the one being replaced
388                 if len(keys) != len(val):
389                     raise ValueError('attempt to assign sequence of size %s '
390                         'to extended slice of size %s' % (len(val), len(keys)))
391                 # FIXME: efficiency?
392                 del self[key]
393                 item_list = zip(indexes, val.items())
394                 # smallest indexes first - higher indexes not guaranteed to
395                 # exist
396                 item_list.sort()
397                 for pos, (newkey, newval) in item_list:
398                     if self.strict and newkey in self:
399                         raise ValueError('slice assignment must be from unique'
400                             ' keys')
401                     self.insert(pos, newkey, newval)
402         else:
403             if key not in self:
404                 self._sequence.append(key)
405             dict.__setitem__(self, key, val)
406
407     def __getitem__(self, key):
408         """
409         Allows slicing. Returns an OrderedDict if you slice.
410         >>> b = OrderedDict([(7, 0), (6, 1), (5, 2), (4, 3), (3, 4), (2, 5), (1, 6)])
411         >>> b[::-1]
412         OrderedDict([(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1), (7, 0)])
413         >>> b[2:5]
414         OrderedDict([(5, 2), (4, 3), (3, 4)])
415         >>> type(b[2:4])
416         <class '__main__.OrderedDict'>
417         """
418         if isinstance(key, types.SliceType):
419             # FIXME: does this raise the error we want?
420             keys = self._sequence[key]
421             # FIXME: efficiency?
422             return OrderedDict([(entry, self[entry]) for entry in keys])
423         else:
424             return dict.__getitem__(self, key)
425
426     __str__ = __repr__
427
428     def __setattr__(self, name, value):
429         """
430         Implemented so that accesses to ``sequence`` raise a warning and are
431         diverted to the new ``setkeys`` method.
432         """
433         if name == 'sequence':
434             warnings.warn('Use of the sequence attribute is deprecated.'
435                 ' Use the keys method instead.', DeprecationWarning)
436             # NOTE: doesn't return anything
437             self.setkeys(value)
438         else:
439             # FIXME: do we want to allow arbitrary setting of attributes?
440             #   Or do we want to manage it?
441             object.__setattr__(self, name, value)
442
443     def __getattr__(self, name):
444         """
445         Implemented so that access to ``sequence`` raises a warning.
446         
447         >>> d = OrderedDict()
448         >>> d.sequence
449         []
450         """
451         if name == 'sequence':
452             warnings.warn('Use of the sequence attribute is deprecated.'
453                 ' Use the keys method instead.', DeprecationWarning)
454             # NOTE: Still (currently) returns a direct reference. Need to
455             #   because code that uses sequence will expect to be able to
456             #   mutate it in place.
457             return self._sequence
458         else:
459             # raise the appropriate error
460             raise AttributeError("OrderedDict has no '%s' attribute" % name)
461
462     def __deepcopy__(self, memo):
463         """
464         To allow deepcopy to work with OrderedDict.
465         
466         >>> from copy import deepcopy
467         >>> a = OrderedDict([(1, 1), (2, 2), (3, 3)])
468         >>> a['test'] = {}
469         >>> b = deepcopy(a)
470         >>> b == a
471         True
472         >>> b is a
473         False
474         >>> a['test'] is b['test']
475         False
476         """
477         from copy import deepcopy
478         return self.__class__(deepcopy(self.items(), memo), self.strict)
479
480
481 ### Read-only methods ###
482
483     def copy(self):
484         """
485         >>> OrderedDict(((1, 3), (3, 2), (2, 1))).copy()
486         OrderedDict([(1, 3), (3, 2), (2, 1)])
487         """
488         return OrderedDict(self)
489
490     def items(self):
491         """
492         ``items`` returns a list of tuples representing all the 
493         ``(key, value)`` pairs in the dictionary.
494         
495         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
496         >>> d.items()
497         [(1, 3), (3, 2), (2, 1)]
498         >>> d.clear()
499         >>> d.items()
500         []
501         """
502         return zip(self._sequence, self.values())
503
504     def keys(self):
505         """
506         Return a list of keys in the ``OrderedDict``.
507         
508         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
509         >>> d.keys()
510         [1, 3, 2]
511         """
512         return self._sequence[:]
513
514     def values(self, values=None):
515         """
516         Return a list of all the values in the OrderedDict.
517         
518         Optionally you can pass in a list of values, which will replace the
519         current list. The value list must be the same len as the OrderedDict.
520         
521         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
522         >>> d.values()
523         [3, 2, 1]
524         """
525         return [self[key] for key in self._sequence]
526
527     def iteritems(self):
528         """
529         >>> ii = OrderedDict(((1, 3), (3, 2), (2, 1))).iteritems()
530         >>> ii.next()
531         (1, 3)
532         >>> ii.next()
533         (3, 2)
534         >>> ii.next()
535         (2, 1)
536         >>> ii.next()
537         Traceback (most recent call last):
538         StopIteration
539         """
540         def make_iter(self=self):
541             keys = self.iterkeys()
542             while True:
543                 key = keys.next()
544                 yield (key, self[key])
545         return make_iter()
546
547     def iterkeys(self):
548         """
549         >>> ii = OrderedDict(((1, 3), (3, 2), (2, 1))).iterkeys()
550         >>> ii.next()
551         1
552         >>> ii.next()
553         3
554         >>> ii.next()
555         2
556         >>> ii.next()
557         Traceback (most recent call last):
558         StopIteration
559         """
560         return iter(self._sequence)
561
562     __iter__ = iterkeys
563
564     def itervalues(self):
565         """
566         >>> iv = OrderedDict(((1, 3), (3, 2), (2, 1))).itervalues()
567         >>> iv.next()
568         3
569         >>> iv.next()
570         2
571         >>> iv.next()
572         1
573         >>> iv.next()
574         Traceback (most recent call last):
575         StopIteration
576         """
577         def make_iter(self=self):
578             keys = self.iterkeys()
579             while True:
580                 yield self[keys.next()]
581         return make_iter()
582
583 ### Read-write methods ###
584
585     def clear(self):
586         """
587         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
588         >>> d.clear()
589         >>> d
590         OrderedDict([])
591         """
592         dict.clear(self)
593         self._sequence = []
594
595     def pop(self, key, *args):
596         """
597         No dict.pop in Python 2.2, gotta reimplement it
598         
599         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
600         >>> d.pop(3)
601         2
602         >>> d
603         OrderedDict([(1, 3), (2, 1)])
604         >>> d.pop(4)
605         Traceback (most recent call last):
606         KeyError: 4
607         >>> d.pop(4, 0)
608         0
609         >>> d.pop(4, 0, 1)
610         Traceback (most recent call last):
611         TypeError: pop expected at most 2 arguments, got 3
612         """
613         if len(args) > 1:
614             raise TypeError, ('pop expected at most 2 arguments, got %s' %
615                 (len(args) + 1))
616         if key in self:
617             val = self[key]
618             del self[key]
619         else:
620             try:
621                 val = args[0]
622             except IndexError:
623                 raise KeyError(key)
624         return val
625
626     def popitem(self, i=-1):
627         """
628         Delete and return an item specified by index, not a random one as in
629         dict. The index is -1 by default (the last item).
630         
631         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
632         >>> d.popitem()
633         (2, 1)
634         >>> d
635         OrderedDict([(1, 3), (3, 2)])
636         >>> d.popitem(0)
637         (1, 3)
638         >>> OrderedDict().popitem()
639         Traceback (most recent call last):
640         KeyError: 'popitem(): dictionary is empty'
641         >>> d.popitem(2)
642         Traceback (most recent call last):
643         IndexError: popitem(): index 2 not valid
644         """
645         if not self._sequence:
646             raise KeyError('popitem(): dictionary is empty')
647         try:
648             key = self._sequence[i]
649         except IndexError:
650             raise IndexError('popitem(): index %s not valid' % i)
651         return (key, self.pop(key))
652
653     def setdefault(self, key, defval = None):
654         """
655         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
656         >>> d.setdefault(1)
657         3
658         >>> d.setdefault(4) is None
659         True
660         >>> d
661         OrderedDict([(1, 3), (3, 2), (2, 1), (4, None)])
662         >>> d.setdefault(5, 0)
663         0
664         >>> d
665         OrderedDict([(1, 3), (3, 2), (2, 1), (4, None), (5, 0)])
666         """
667         if key in self:
668             return self[key]
669         else:
670             self[key] = defval
671             return defval
672
673     def update(self, from_od):
674         """
675         Update from another OrderedDict or sequence of (key, value) pairs
676         
677         >>> d = OrderedDict(((1, 0), (0, 1)))
678         >>> d.update(OrderedDict(((1, 3), (3, 2), (2, 1))))
679         >>> d
680         OrderedDict([(1, 3), (0, 1), (3, 2), (2, 1)])
681         >>> d.update({4: 4})
682         Traceback (most recent call last):
683         TypeError: undefined order, cannot get items from dict
684         >>> d.update((4, 4))
685         Traceback (most recent call last):
686         TypeError: cannot convert dictionary update sequence element "4" to a 2-item sequence
687         """
688         if isinstance(from_od, OrderedDict):
689             for key, val in from_od.items():
690                 self[key] = val
691         elif isinstance(from_od, dict):
692             # we lose compatibility with other ordered dict types this way
693             raise TypeError('undefined order, cannot get items from dict')
694         else:
695             # FIXME: efficiency?
696             # sequence of 2-item sequences, or error
697             for item in from_od:
698                 try:
699                     key, val = item
700                 except TypeError:
701                     raise TypeError('cannot convert dictionary update'
702                         ' sequence element "%s" to a 2-item sequence' % item)
703                 self[key] = val
704
705     def rename(self, old_key, new_key):
706         """
707         Rename the key for a given value, without modifying sequence order.
708         
709         For the case where new_key already exists this raise an exception,
710         since if new_key exists, it is ambiguous as to what happens to the
711         associated values, and the position of new_key in the sequence.
712         
713         >>> od = OrderedDict()
714         >>> od['a'] = 1
715         >>> od['b'] = 2
716         >>> od.items()
717         [('a', 1), ('b', 2)]
718         >>> od.rename('b', 'c')
719         >>> od.items()
720         [('a', 1), ('c', 2)]
721         >>> od.rename('c', 'a')
722         Traceback (most recent call last):
723         ValueError: New key already exists: 'a'
724         >>> od.rename('d', 'b')
725         Traceback (most recent call last):
726         KeyError: 'd'
727         """
728         if new_key == old_key:
729             # no-op
730             return
731         if new_key in self:
732             raise ValueError("New key already exists: %r" % new_key)
733         # rename sequence entry
734         value = self[old_key] 
735         old_idx = self._sequence.index(old_key)
736         self._sequence[old_idx] = new_key
737         # rename internal dict entry
738         dict.__delitem__(self, old_key)
739         dict.__setitem__(self, new_key, value)
740
741     def setitems(self, items):
742         """
743         This method allows you to set the items in the dict.
744         
745         It takes a list of tuples - of the same sort returned by the ``items``
746         method.
747         
748         >>> d = OrderedDict()
749         >>> d.setitems(((3, 1), (2, 3), (1, 2)))
750         >>> d
751         OrderedDict([(3, 1), (2, 3), (1, 2)])
752         """
753         self.clear()
754         # FIXME: this allows you to pass in an OrderedDict as well :-)
755         self.update(items)
756
757     def setkeys(self, keys):
758         """
759         ``setkeys`` all ows you to pass in a new list of keys which will
760         replace the current set. This must contain the same set of keys, but
761         need not be in the same order.
762         
763         If you pass in new keys that don't match, a ``KeyError`` will be
764         raised.
765         
766         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
767         >>> d.keys()
768         [1, 3, 2]
769         >>> d.setkeys((1, 2, 3))
770         >>> d
771         OrderedDict([(1, 3), (2, 1), (3, 2)])
772         >>> d.setkeys(['a', 'b', 'c'])
773         Traceback (most recent call last):
774         KeyError: 'Keylist is not the same as current keylist.'
775         """
776         # FIXME: Efficiency? (use set for Python 2.4 :-)
777         # NOTE: list(keys) rather than keys[:] because keys[:] returns
778         #   a tuple, if keys is a tuple.
779         kcopy = list(keys)
780         kcopy.sort()
781         self._sequence.sort()
782         if kcopy != self._sequence:
783             raise KeyError('Keylist is not the same as current keylist.')
784         # NOTE: This makes the _sequence attribute a new object, instead
785         #       of changing it in place.
786         # FIXME: efficiency?
787         self._sequence = list(keys)
788
789     def setvalues(self, values):
790         """
791         You can pass in a list of values, which will replace the
792         current list. The value list must be the same len as the OrderedDict.
793         
794         (Or a ``ValueError`` is raised.)
795         
796         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
797         >>> d.setvalues((1, 2, 3))
798         >>> d
799         OrderedDict([(1, 1), (3, 2), (2, 3)])
800         >>> d.setvalues([6])
801         Traceback (most recent call last):
802         ValueError: Value list is not the same length as the OrderedDict.
803         """
804         if len(values) != len(self):
805             # FIXME: correct error to raise?
806             raise ValueError('Value list is not the same length as the '
807                 'OrderedDict.')
808         self.update(zip(self, values))
809
810 ### Sequence Methods ###
811
812     def index(self, key):
813         """
814         Return the position of the specified key in the OrderedDict.
815         
816         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
817         >>> d.index(3)
818         1
819         >>> d.index(4)
820         Traceback (most recent call last):
821         ValueError: list.index(x): x not in list
822         """
823         return self._sequence.index(key)
824
825     def insert(self, index, key, value):
826         """
827         Takes ``index``, ``key``, and ``value`` as arguments.
828         
829         Sets ``key`` to ``value``, so that ``key`` is at position ``index`` in
830         the OrderedDict.
831         
832         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
833         >>> d.insert(0, 4, 0)
834         >>> d
835         OrderedDict([(4, 0), (1, 3), (3, 2), (2, 1)])
836         >>> d.insert(0, 2, 1)
837         >>> d
838         OrderedDict([(2, 1), (4, 0), (1, 3), (3, 2)])
839         >>> d.insert(8, 8, 1)
840         >>> d
841         OrderedDict([(2, 1), (4, 0), (1, 3), (3, 2), (8, 1)])
842         """
843         if key in self:
844             # FIXME: efficiency?
845             del self[key]
846         self._sequence.insert(index, key)
847         dict.__setitem__(self, key, value)
848
849     def reverse(self):
850         """
851         Reverse the order of the OrderedDict.
852         
853         >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
854         >>> d.reverse()
855         >>> d
856         OrderedDict([(2, 1), (3, 2), (1, 3)])
857         """
858         self._sequence.reverse()
859
860     def sort(self, *args, **kwargs):
861         """
862         Sort the key order in the OrderedDict.
863         
864         This method takes the same arguments as the ``list.sort`` method on
865         your version of Python.
866         
867         >>> d = OrderedDict(((4, 1), (2, 2), (3, 3), (1, 4)))
868         >>> d.sort()
869         >>> d
870         OrderedDict([(1, 4), (2, 2), (3, 3), (4, 1)])
871         """
872         self._sequence.sort(*args, **kwargs)
873
874 class Keys(object):
875     # FIXME: should this object be a subclass of list?
876     """
877     Custom object for accessing the keys of an OrderedDict.
878     
879     Can be called like the normal ``OrderedDict.keys`` method, but also
880     supports indexing and sequence methods.
881     """
882
883     def __init__(self, main):
884         self._main = main
885
886     def __call__(self):
887         """Pretend to be the keys method."""
888         return self._main._keys()
889
890     def __getitem__(self, index):
891         """Fetch the key at position i."""
892         # NOTE: this automatically supports slicing :-)
893         return self._main._sequence[index]
894
895     def __setitem__(self, index, name):
896         """
897         You cannot assign to keys, but you can do slice assignment to re-order
898         them.
899         
900         You can only do slice assignment if the new set of keys is a reordering
901         of the original set.
902         """
903         if isinstance(index, types.SliceType):
904             # FIXME: efficiency?
905             # check length is the same
906             indexes = range(len(self._main._sequence))[index]
907             if len(indexes) != len(name):
908                 raise ValueError('attempt to assign sequence of size %s '
909                     'to slice of size %s' % (len(name), len(indexes)))
910             # check they are the same keys
911             # FIXME: Use set
912             old_keys = self._main._sequence[index]
913             new_keys = list(name)
914             old_keys.sort()
915             new_keys.sort()
916             if old_keys != new_keys:
917                 raise KeyError('Keylist is not the same as current keylist.')
918             orig_vals = [self._main[k] for k in name]
919             del self._main[index]
920             vals = zip(indexes, name, orig_vals)
921             vals.sort()
922             for i, k, v in vals:
923                 if self._main.strict and k in self._main:
924                     raise ValueError('slice assignment must be from '
925                         'unique keys')
926                 self._main.insert(i, k, v)
927         else:
928             raise ValueError('Cannot assign to keys')
929
930     ### following methods pinched from UserList and adapted ###
931     def __repr__(self): return repr(self._main._sequence)
932
933     # FIXME: do we need to check if we are comparing with another ``Keys``
934     #   object? (like the __cast method of UserList)
935     def __lt__(self, other): return self._main._sequence <  other
936     def __le__(self, other): return self._main._sequence <= other
937     def __eq__(self, other): return self._main._sequence == other
938     def __ne__(self, other): return self._main._sequence != other
939     def __gt__(self, other): return self._main._sequence >  other
940     def __ge__(self, other): return self._main._sequence >= other
941     # FIXME: do we need __cmp__ as well as rich comparisons?
942     def __cmp__(self, other): return cmp(self._main._sequence, other)
943
944     def __contains__(self, item): return item in self._main._sequence
945     def __len__(self): return len(self._main._sequence)
946     def __iter__(self): return self._main.iterkeys()
947     def count(self, item): return self._main._sequence.count(item)
948     def index(self, item, *args): return self._main._sequence.index(item, *args)
949     def reverse(self): self._main._sequence.reverse()
950     def sort(self, *args, **kwds): self._main._sequence.sort(*args, **kwds)
951     def __mul__(self, n): return self._main._sequence*n
952     __rmul__ = __mul__
953     def __add__(self, other): return self._main._sequence + other
954     def __radd__(self, other): return other + self._main._sequence
955
956     ## following methods not implemented for keys ##
957     def __delitem__(self, i): raise TypeError('Can\'t delete items from keys')
958     def __iadd__(self, other): raise TypeError('Can\'t add in place to keys')
959     def __imul__(self, n): raise TypeError('Can\'t multiply keys in place')
960     def append(self, item): raise TypeError('Can\'t append items to keys')
961     def insert(self, i, item): raise TypeError('Can\'t insert items into keys')
962     def pop(self, i=-1): raise TypeError('Can\'t pop items from keys')
963     def remove(self, item): raise TypeError('Can\'t remove items from keys')
964     def extend(self, other): raise TypeError('Can\'t extend keys')
965
966 class Items(object):
967     """
968     Custom object for accessing the items of an OrderedDict.
969     
970     Can be called like the normal ``OrderedDict.items`` method, but also
971     supports indexing and sequence methods.
972     """
973
974     def __init__(self, main):
975         self._main = main
976
977     def __call__(self):
978         """Pretend to be the items method."""
979         return self._main._items()
980
981     def __getitem__(self, index):
982         """Fetch the item at position i."""
983         if isinstance(index, types.SliceType):
984             # fetching a slice returns an OrderedDict
985             return self._main[index].items()
986         key = self._main._sequence[index]
987         return (key, self._main[key])
988
989     def __setitem__(self, index, item):
990         """Set item at position i to item."""
991         if isinstance(index, types.SliceType):
992             # NOTE: item must be an iterable (list of tuples)
993             self._main[index] = OrderedDict(item)
994         else:
995             # FIXME: Does this raise a sensible error?
996             orig = self._main.keys[index]
997             key, value = item
998             if self._main.strict and key in self and (key != orig):
999                 raise ValueError('slice assignment must be from '
1000                         'unique keys')
1001             # delete the current one
1002             del self._main[self._main._sequence[index]]
1003             self._main.insert(index, key, value)
1004
1005     def __delitem__(self, i):
1006         """Delete the item at position i."""
1007         key = self._main._sequence[i]
1008         if isinstance(i, types.SliceType):
1009             for k in key:
1010                 # FIXME: efficiency?
1011                 del self._main[k]
1012         else:
1013             del self._main[key]
1014
1015     ### following methods pinched from UserList and adapted ###
1016     def __repr__(self): return repr(self._main.items())
1017
1018     # FIXME: do we need to check if we are comparing with another ``Items``
1019     #   object? (like the __cast method of UserList)
1020     def __lt__(self, other): return self._main.items() <  other
1021     def __le__(self, other): return self._main.items() <= other
1022     def __eq__(self, other): return self._main.items() == other
1023     def __ne__(self, other): return self._main.items() != other
1024     def __gt__(self, other): return self._main.items() >  other
1025     def __ge__(self, other): return self._main.items() >= other
1026     def __cmp__(self, other): return cmp(self._main.items(), other)
1027
1028     def __contains__(self, item): return item in self._main.items()
1029     def __len__(self): return len(self._main._sequence) # easier :-)
1030     def __iter__(self): return self._main.iteritems()
1031     def count(self, item): return self._main.items().count(item)
1032     def index(self, item, *args): return self._main.items().index(item, *args)
1033     def reverse(self): self._main.reverse()
1034     def sort(self, *args, **kwds): self._main.sort(*args, **kwds)
1035     def __mul__(self, n): return self._main.items()*n
1036     __rmul__ = __mul__
1037     def __add__(self, other): return self._main.items() + other
1038     def __radd__(self, other): return other + self._main.items()
1039
1040     def append(self, item):
1041         """Add an item to the end."""
1042         # FIXME: this is only append if the key isn't already present
1043         key, value = item
1044         self._main[key] = value
1045
1046     def insert(self, i, item):
1047         key, value = item
1048         self._main.insert(i, key, value)
1049
1050     def pop(self, i=-1):
1051         key = self._main._sequence[i]
1052         return (key, self._main.pop(key))
1053
1054     def remove(self, item):
1055         key, value = item
1056         try:
1057             assert value == self._main[key]
1058         except (KeyError, AssertionError):
1059             raise ValueError('ValueError: list.remove(x): x not in list')
1060         else:
1061             del self._main[key]
1062
1063     def extend(self, other):
1064         # FIXME: is only a true extend if none of the keys already present
1065         for item in other:
1066             key, value = item
1067             self._main[key] = value
1068
1069     def __iadd__(self, other):
1070         self.extend(other)
1071
1072     ## following methods not implemented for items ##
1073
1074     def __imul__(self, n): raise TypeError('Can\'t multiply items in place')
1075
1076 class Values(object):
1077     """
1078     Custom object for accessing the values of an OrderedDict.
1079     
1080     Can be called like the normal ``OrderedDict.values`` method, but also
1081     supports indexing and sequence methods.
1082     """
1083
1084     def __init__(self, main):
1085         self._main = main
1086
1087     def __call__(self):
1088         """Pretend to be the values method."""
1089         return self._main._values()
1090
1091     def __getitem__(self, index):
1092         """Fetch the value at position i."""
1093         if isinstance(index, types.SliceType):
1094             return [self._main[key] for key in self._main._sequence[index]]
1095         else:
1096             return self._main[self._main._sequence[index]]
1097
1098     def __setitem__(self, index, value):
1099         """
1100         Set the value at position i to value.
1101         
1102         You can only do slice assignment to values if you supply a sequence of
1103         equal length to the slice you are replacing.
1104         """
1105         if isinstance(index, types.SliceType):
1106             keys = self._main._sequence[index]
1107             if len(keys) != len(value):
1108                 raise ValueError('attempt to assign sequence of size %s '
1109                     'to slice of size %s' % (len(name), len(keys)))
1110             # FIXME: efficiency?  Would be better to calculate the indexes
1111             #   directly from the slice object
1112             # NOTE: the new keys can collide with existing keys (or even
1113             #   contain duplicates) - these will overwrite
1114             for key, val in zip(keys, value):
1115                 self._main[key] = val
1116         else:
1117             self._main[self._main._sequence[index]] = value
1118
1119     ### following methods pinched from UserList and adapted ###
1120     def __repr__(self): return repr(self._main.values())
1121
1122     # FIXME: do we need to check if we are comparing with another ``Values``
1123     #   object? (like the __cast method of UserList)
1124     def __lt__(self, other): return self._main.values() <  other
1125     def __le__(self, other): return self._main.values() <= other
1126     def __eq__(self, other): return self._main.values() == other
1127     def __ne__(self, other): return self._main.values() != other
1128     def __gt__(self, other): return self._main.values() >  other
1129     def __ge__(self, other): return self._main.values() >= other
1130     def __cmp__(self, other): return cmp(self._main.values(), other)
1131
1132     def __contains__(self, item): return item in self._main.values()
1133     def __len__(self): return len(self._main._sequence) # easier :-)
1134     def __iter__(self): return self._main.itervalues()
1135     def count(self, item): return self._main.values().count(item)
1136     def index(self, item, *args): return self._main.values().index(item, *args)
1137
1138     def reverse(self):
1139         """Reverse the values"""
1140         vals = self._main.values()
1141         vals.reverse()
1142         # FIXME: efficiency
1143         self[:] = vals
1144
1145     def sort(self, *args, **kwds):
1146         """Sort the values."""
1147         vals = self._main.values()
1148         vals.sort(*args, **kwds)
1149         self[:] = vals
1150
1151     def __mul__(self, n): return self._main.values()*n
1152     __rmul__ = __mul__
1153     def __add__(self, other): return self._main.values() + other
1154     def __radd__(self, other): return other + self._main.values()
1155
1156     ## following methods not implemented for values ##
1157     def __delitem__(self, i): raise TypeError('Can\'t delete items from values')
1158     def __iadd__(self, other): raise TypeError('Can\'t add in place to values')
1159     def __imul__(self, n): raise TypeError('Can\'t multiply values in place')
1160     def append(self, item): raise TypeError('Can\'t append items to values')
1161     def insert(self, i, item): raise TypeError('Can\'t insert items into values')
1162     def pop(self, i=-1): raise TypeError('Can\'t pop items from values')
1163     def remove(self, item): raise TypeError('Can\'t remove items from values')
1164     def extend(self, other): raise TypeError('Can\'t extend values')
1165
1166 class SequenceOrderedDict(OrderedDict):
1167     """
1168     Experimental version of OrderedDict that has a custom object for ``keys``,
1169     ``values``, and ``items``.
1170     
1171     These are callable sequence objects that work as methods, or can be
1172     manipulated directly as sequences.
1173     
1174     Test for ``keys``, ``items`` and ``values``.
1175     
1176     >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)))
1177     >>> d
1178     SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
1179     >>> d.keys
1180     [1, 2, 3]
1181     >>> d.keys()
1182     [1, 2, 3]
1183     >>> d.setkeys((3, 2, 1))
1184     >>> d
1185     SequenceOrderedDict([(3, 4), (2, 3), (1, 2)])
1186     >>> d.setkeys((1, 2, 3))
1187     >>> d.keys[0]
1188     1
1189     >>> d.keys[:]
1190     [1, 2, 3]
1191     >>> d.keys[-1]
1192     3
1193     >>> d.keys[-2]
1194     2
1195     >>> d.keys[0:2] = [2, 1]
1196     >>> d
1197     SequenceOrderedDict([(2, 3), (1, 2), (3, 4)])
1198     >>> d.keys.reverse()
1199     >>> d.keys
1200     [3, 1, 2]
1201     >>> d.keys = [1, 2, 3]
1202     >>> d
1203     SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
1204     >>> d.keys = [3, 1, 2]
1205     >>> d
1206     SequenceOrderedDict([(3, 4), (1, 2), (2, 3)])
1207     >>> a = SequenceOrderedDict()
1208     >>> b = SequenceOrderedDict()
1209     >>> a.keys == b.keys
1210     1
1211     >>> a['a'] = 3
1212     >>> a.keys == b.keys
1213     0
1214     >>> b['a'] = 3
1215     >>> a.keys == b.keys
1216     1
1217     >>> b['b'] = 3
1218     >>> a.keys == b.keys
1219     0
1220     >>> a.keys > b.keys
1221     0
1222     >>> a.keys < b.keys
1223     1
1224     >>> 'a' in a.keys
1225     1
1226     >>> len(b.keys)
1227     2
1228     >>> 'c' in d.keys
1229     0
1230     >>> 1 in d.keys
1231     1
1232     >>> [v for v in d.keys]
1233     [3, 1, 2]
1234     >>> d.keys.sort()
1235     >>> d.keys
1236     [1, 2, 3]
1237     >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)), strict=True)
1238     >>> d.keys[::-1] = [1, 2, 3]
1239     >>> d
1240     SequenceOrderedDict([(3, 4), (2, 3), (1, 2)])
1241     >>> d.keys[:2]
1242     [3, 2]
1243     >>> d.keys[:2] = [1, 3]
1244     Traceback (most recent call last):
1245     KeyError: 'Keylist is not the same as current keylist.'
1246
1247     >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)))
1248     >>> d
1249     SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
1250     >>> d.values
1251     [2, 3, 4]
1252     >>> d.values()
1253     [2, 3, 4]
1254     >>> d.setvalues((4, 3, 2))
1255     >>> d
1256     SequenceOrderedDict([(1, 4), (2, 3), (3, 2)])
1257     >>> d.values[::-1]
1258     [2, 3, 4]
1259     >>> d.values[0]
1260     4
1261     >>> d.values[-2]
1262     3
1263     >>> del d.values[0]
1264     Traceback (most recent call last):
1265     TypeError: Can't delete items from values
1266     >>> d.values[::2] = [2, 4]
1267     >>> d
1268     SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
1269     >>> 7 in d.values
1270     0
1271     >>> len(d.values)
1272     3
1273     >>> [val for val in d.values]
1274     [2, 3, 4]
1275     >>> d.values[-1] = 2
1276     >>> d.values.count(2)
1277     2
1278     >>> d.values.index(2)
1279     0
1280     >>> d.values[-1] = 7
1281     >>> d.values
1282     [2, 3, 7]
1283     >>> d.values.reverse()
1284     >>> d.values
1285     [7, 3, 2]
1286     >>> d.values.sort()
1287     >>> d.values
1288     [2, 3, 7]
1289     >>> d.values.append('anything')
1290     Traceback (most recent call last):
1291     TypeError: Can't append items to values
1292     >>> d.values = (1, 2, 3)
1293     >>> d
1294     SequenceOrderedDict([(1, 1), (2, 2), (3, 3)])
1295     
1296     >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)))
1297     >>> d
1298     SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
1299     >>> d.items()
1300     [(1, 2), (2, 3), (3, 4)]
1301     >>> d.setitems([(3, 4), (2 ,3), (1, 2)])
1302     >>> d
1303     SequenceOrderedDict([(3, 4), (2, 3), (1, 2)])
1304     >>> d.items[0]
1305     (3, 4)
1306     >>> d.items[:-1]
1307     [(3, 4), (2, 3)]
1308     >>> d.items[1] = (6, 3)
1309     >>> d.items
1310     [(3, 4), (6, 3), (1, 2)]
1311     >>> d.items[1:2] = [(9, 9)]
1312     >>> d
1313     SequenceOrderedDict([(3, 4), (9, 9), (1, 2)])
1314     >>> del d.items[1:2]
1315     >>> d
1316     SequenceOrderedDict([(3, 4), (1, 2)])
1317     >>> (3, 4) in d.items
1318     1
1319     >>> (4, 3) in d.items
1320     0
1321     >>> len(d.items)
1322     2
1323     >>> [v for v in d.items]
1324     [(3, 4), (1, 2)]
1325     >>> d.items.count((3, 4))
1326     1
1327     >>> d.items.index((1, 2))
1328     1
1329     >>> d.items.index((2, 1))
1330     Traceback (most recent call last):
1331     ValueError: list.index(x): x not in list
1332     >>> d.items.reverse()
1333     >>> d.items
1334     [(1, 2), (3, 4)]
1335     >>> d.items.reverse()
1336     >>> d.items.sort()
1337     >>> d.items
1338     [(1, 2), (3, 4)]
1339     >>> d.items.append((5, 6))
1340     >>> d.items
1341     [(1, 2), (3, 4), (5, 6)]
1342     >>> d.items.insert(0, (0, 0))
1343     >>> d.items
1344     [(0, 0), (1, 2), (3, 4), (5, 6)]
1345     >>> d.items.insert(-1, (7, 8))
1346     >>> d.items
1347     [(0, 0), (1, 2), (3, 4), (7, 8), (5, 6)]
1348     >>> d.items.pop()
1349     (5, 6)
1350     >>> d.items
1351     [(0, 0), (1, 2), (3, 4), (7, 8)]
1352     >>> d.items.remove((1, 2))
1353     >>> d.items
1354     [(0, 0), (3, 4), (7, 8)]
1355     >>> d.items.extend([(1, 2), (5, 6)])
1356     >>> d.items
1357     [(0, 0), (3, 4), (7, 8), (1, 2), (5, 6)]
1358     """
1359
1360     def __init__(self, init_val=(), strict=True):
1361         OrderedDict.__init__(self, init_val, strict=strict)
1362         self._keys = self.keys
1363         self._values = self.values
1364         self._items = self.items
1365         self.keys = Keys(self)
1366         self.values = Values(self)
1367         self.items = Items(self)
1368         self._att_dict = {
1369             'keys': self.setkeys,
1370             'items': self.setitems,
1371             'values': self.setvalues,
1372         }
1373
1374     def __setattr__(self, name, value):
1375         """Protect keys, items, and values."""
1376         if not '_att_dict' in self.__dict__:
1377             object.__setattr__(self, name, value)
1378         else:
1379             try:
1380                 fun = self._att_dict[name]
1381             except KeyError:
1382                 OrderedDict.__setattr__(self, name, value)
1383             else:
1384                 fun(value)
1385
1386 if __name__ == '__main__':
1387     if INTP_VER < (2, 3):
1388         raise RuntimeError("Tests require Python v.2.3 or later")
1389     # turn off warnings for tests
1390     warnings.filterwarnings('ignore')
1391     # run the code tests in doctest format
1392     import doctest
1393     m = sys.modules.get('__main__')
1394     globs = m.__dict__.copy()
1395     globs.update({
1396         'INTP_VER': INTP_VER,
1397     })
1398     doctest.testmod(m, globs=globs)
1399