Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / commissioning / utils / ordereddict.py @ b4368e33

History | View | Annotate | Download (10.2 kB)

1
#!/usr/bin/env python
2

    
3
# Copyright 2012 GRNET S.A. All rights reserved.
4
#
5
# Redistribution and use in source and binary forms, with or
6
# without modification, are permitted provided that the following
7
# conditions are met:
8
#
9
#   1. Redistributions of source code must retain the above
10
#      copyright notice, this list of conditions and the following
11
#      disclaimer.
12
#
13
#   2. Redistributions in binary form must reproduce the above
14
#      copyright notice, this list of conditions and the following
15
#      disclaimer in the documentation and/or other materials
16
#      provided with the distribution.
17
#
18
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
# POSSIBILITY OF SUCH DAMAGE.
30
#
31
# The views and conclusions contained in the software and
32
# documentation are those of the authors and should not be
33
# interpreted as representing official policies, either expressed
34
# or implied, of GRNET S.A.
35

    
36
# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and
37
# pypy. Passes Python2.7's test suite and incorporates all the latest updates.
38

    
39
try:
40
    from thread import get_ident as _get_ident
41
except ImportError:
42
    from dummy_thread import get_ident as _get_ident
43

    
44
try:
45
    from _abcoll import KeysView, ValuesView, ItemsView
46
except ImportError:
47
    pass
48

    
49

    
50
class OrderedDict(dict):
51
    'Dictionary that remembers insertion order'
52
    # An inherited dict maps keys to values.
53
    # The inherited dict provides __getitem__, __len__, __contains__, and get.
54
    # The remaining methods are order-aware.
55
    # Big-O running times for all methods are the same as for regular
56
    # dictionaries.
57

    
58
    # The internal self.__map dictionary maps keys to links in a doubly linked 
59
    # list.
60
    # The circular doubly linked list starts and ends with a sentinel element.
61
    # The sentinel element never gets deleted (this simplifies the algorithm).
62
    # Each link is stored as a list of length three:  [PREV, NEXT, KEY].
63

    
64
    def __init__(self, *args, **kwds):
65
        '''Initialize an ordered dictionary.  Signature is the same as for
66
        regular dictionaries, but keyword arguments are not recommended
67
        because their insertion order is arbitrary.
68

69
        '''
70
        if len(args) > 1:
71
            raise TypeError('expected at most 1 arguments, got %d' % len(args))
72
        try:
73
            self.__root
74
        except AttributeError:
75
            self.__root = root = []                     # sentinel node
76
            root[:] = [root, root, None]
77
            self.__map = {}
78
        self.__update(*args, **kwds)
79

    
80
    def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
81
        'od.__setitem__(i, y) <==> od[i]=y'
82
        # Setting a new item creates a new link which goes at the end of the
83
        # linked
84
        # list, and the inherited dictionary is updated with the new key/value
85
        # pair.
86
        if key not in self:
87
            root = self.__root
88
            last = root[0]
89
            last[1] = root[0] = self.__map[key] = [last, root, key]
90
        dict_setitem(self, key, value)
91

    
92
    def __delitem__(self, key, dict_delitem=dict.__delitem__):
93
        'od.__delitem__(y) <==> del od[y]'
94
        # Deleting an existing item uses self.__map to find the link which is
95
        # then removed by updating the links in the predecessor and successor
96
        # nodes.
97
        dict_delitem(self, key)
98
        link_prev, link_next, key = self.__map.pop(key)
99
        link_prev[1] = link_next
100
        link_next[0] = link_prev
101

    
102
    def __iter__(self):
103
        'od.__iter__() <==> iter(od)'
104
        root = self.__root
105
        curr = root[1]
106
        while curr is not root:
107
            yield curr[2]
108
            curr = curr[1]
109

    
110
    def __reversed__(self):
111
        'od.__reversed__() <==> reversed(od)'
112
        root = self.__root
113
        curr = root[0]
114
        while curr is not root:
115
            yield curr[2]
116
            curr = curr[0]
117

    
118
    def clear(self):
119
        'od.clear() -> None.  Remove all items from od.'
120
        try:
121
            for node in self.__map.itervalues():
122
                del node[:]
123
            root = self.__root
124
            root[:] = [root, root, None]
125
            self.__map.clear()
126
        except AttributeError:
127
            pass
128
        dict.clear(self)
129

    
130
    def popitem(self, last=True):
131
        '''od.popitem() -> (k, v), return and remove a (key, value) pair.
132
        Pairs are returned in LIFO order if last is true or FIFO order if false.
133

134
        '''
135
        if not self:
136
            raise KeyError('dictionary is empty')
137
        root = self.__root
138
        if last:
139
            link = root[0]
140
            link_prev = link[0]
141
            link_prev[1] = root
142
            root[0] = link_prev
143
        else:
144
            link = root[1]
145
            link_next = link[1]
146
            root[1] = link_next
147
            link_next[0] = root
148
        key = link[2]
149
        del self.__map[key]
150
        value = dict.pop(self, key)
151
        return key, value
152

    
153
    # -- the following methods do not depend on the internal structure --
154

    
155
    def keys(self):
156
        'od.keys() -> list of keys in od'
157
        return list(self)
158

    
159
    def values(self):
160
        'od.values() -> list of values in od'
161
        return [self[key] for key in self]
162

    
163
    def items(self):
164
        'od.items() -> list of (key, value) pairs in od'
165
        return [(key, self[key]) for key in self]
166

    
167
    def iterkeys(self):
168
        'od.iterkeys() -> an iterator over the keys in od'
169
        return iter(self)
170

    
171
    def itervalues(self):
172
        'od.itervalues -> an iterator over the values in od'
173
        for k in self:
174
            yield self[k]
175

    
176
    def iteritems(self):
177
        'od.iteritems -> an iterator over the (key, value) items in od'
178
        for k in self:
179
            yield (k, self[k])
180

    
181
    def update(*args, **kwds):
182
        '''od.update(E, **F) -> None.  Update od from dict/iterable E and F.
183

184
        If E is a dict instance, does:           for k in E: od[k] = E[k]
185
        If E has a .keys() method, does:         for k in E.keys(): od[k]=E[k]
186
        Or if E is an iterable of items, does:   for k, v in E: od[k] = v
187
        In either case, this is followed by:     for k, v in F.items(): od[k]=v
188

189
        '''
190
        if len(args) > 2:
191
            raise TypeError('update() takes at most 2 positional '
192
                            'arguments (%d given)' % (len(args),))
193
        elif not args:
194
            raise TypeError('update() takes at least 1 argument (0 given)')
195
        self = args[0]
196
        # Make progressively weaker assumptions about "other"
197
        other = ()
198
        if len(args) == 2:
199
            other = args[1]
200
        if isinstance(other, dict):
201
            for key in other:
202
                self[key] = other[key]
203
        elif hasattr(other, 'keys'):
204
            for key in other.keys():
205
                self[key] = other[key]
206
        else:
207
            for key, value in other:
208
                self[key] = value
209
        for key, value in kwds.items():
210
            self[key] = value
211

    
212
    __update = update  # let subclasses override update without breaking __init__
213

    
214
    __marker = object()
215

    
216
    def pop(self, key, default=__marker):
217
        '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
218
        If key is not found, d is returned if given, otherwise KeyError is raised.
219

220
        '''
221
        if key in self:
222
            result = self[key]
223
            del self[key]
224
            return result
225
        if default is self.__marker:
226
            raise KeyError(key)
227
        return default
228

    
229
    def setdefault(self, key, default=None):
230
        'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
231
        if key in self:
232
            return self[key]
233
        self[key] = default
234
        return default
235

    
236
    def __repr__(self, _repr_running={}):
237
        'od.__repr__() <==> repr(od)'
238
        call_key = id(self), _get_ident()
239
        if call_key in _repr_running:
240
            return '...'
241
        _repr_running[call_key] = 1
242
        try:
243
            if not self:
244
                return '%s()' % (self.__class__.__name__,)
245
            return '%s(%r)' % (self.__class__.__name__, self.items())
246
        finally:
247
            del _repr_running[call_key]
248

    
249
    def __reduce__(self):
250
        'Return state information for pickling'
251
        items = [[k, self[k]] for k in self]
252
        inst_dict = vars(self).copy()
253
        for k in vars(OrderedDict()):
254
            inst_dict.pop(k, None)
255
        if inst_dict:
256
            return (self.__class__, (items,), inst_dict)
257
        return self.__class__, (items,)
258

    
259
    def copy(self):
260
        'od.copy() -> a shallow copy of od'
261
        return self.__class__(self)
262

    
263
    @classmethod
264
    def fromkeys(cls, iterable, value=None):
265
        '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
266
        and values equal to v (which defaults to None).
267

268
        '''
269
        d = cls()
270
        for key in iterable:
271
            d[key] = value
272
        return d
273

    
274
    def __eq__(self, other):
275
        '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
276
        while comparison to a regular mapping is order-insensitive.
277

278
        '''
279
        if isinstance(other, OrderedDict):
280
            return len(self)==len(other) and self.items() == other.items()
281
        return dict.__eq__(self, other)
282

    
283
    def __ne__(self, other):
284
        return not self == other
285

    
286
    # -- the following methods are only used in Python 2.7 --
287

    
288
    def viewkeys(self):
289
        "od.viewkeys() -> a set-like object providing a view on od's keys"
290
        return KeysView(self)
291

    
292
    def viewvalues(self):
293
        "od.viewvalues() -> an object providing a view on od's values"
294
        return ValuesView(self)
295

    
296
    def viewitems(self):
297
        "od.viewitems() -> a set-like object providing a view on od's items"
298
        return ItemsView(self)