source: sasview/src/sas/sascalc/data_util/odict.py @ 2510b9b

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 2510b9b was b699768, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 9 years ago

Initial commit of the refactored SasCalc? module.

  • Property mode set to 100644
File size: 45.5 KB
Line 
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"""
18from __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 58 2008-09-02 14:09:54Z farrowch $'
26
27__version__ = '0.2.2'
28
29__all__ = ['OrderedDict', 'SequenceOrderedDict']
30
31import sys
32INTP_VER = sys.version_info[:2]
33if INTP_VER < (2, 2):
34    raise RuntimeError("Python v.2.2 or later required")
35
36import types, warnings
37
38class 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
874class 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
966class 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
1076class 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
1166class 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
1386if __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
Note: See TracBrowser for help on using the repository browser.