"""Drop-in replacement for collections.AttributedDict by Raymond Hettinger http://code.activestate.com/recipes/576693/ """ try: try: from collections.abc import MutableMapping as DictMixin except ImportError: from collections import MutableMapping as DictMixin py_level = 3 except ImportError: # noinspection PyUnresolvedReferences,PyCompatibility from UserDict import DictMixin py_level = 2 # Modified from original to support Python 2.4, see # http://code.google.com/p/dirtyjson/issues/detail?id=53 try: # noinspection PyStatementEffect all except NameError: # noinspection PyShadowingBuiltins def all(seq): for elem in seq: if not elem: return False return True class AttributedDict(dict, DictMixin): def __init__(self): super(AttributedDict, self).__init__() self.__attributes = {} self.clear() # noinspection PyAttributeOutsideInit def clear(self): self.__end = end = [] end += [None, end, end] # sentinel node for doubly linked list self.__map = {} # key --> [key, prev, next] dict.clear(self) def add_with_attributes(self, key, value, attributes): self.__setitem__(key, value) self.__attributes[key] = attributes def attributes(self, key): return self.__attributes.get(key) def __setitem__(self, key, value): if key not in self: end = self.__end curr = end[1] curr[2] = end[1] = self.__map[key] = [key, curr, end] dict.__setitem__(self, key, value) def __delitem__(self, key): dict.__delitem__(self, key) key, prev, next_entry = self.__map.pop(key) prev[2] = next_entry next_entry[1] = prev def __iter__(self): end = self.__end curr = end[2] while curr is not end: yield curr[0] curr = curr[2] def __reversed__(self): end = self.__end curr = end[1] while curr is not end: yield curr[0] curr = curr[1] def popitem(self, last=True): if not self: raise KeyError('dictionary is empty') # Modified from original to support Python 2.4, see # http://code.google.com/p/dirtyjson/issues/detail?id=53 if py_level == 2: if last: # noinspection PyUnresolvedReferences key = reversed(self).next() else: # noinspection PyUnresolvedReferences key = iter(self).next() else: if last: key = next(reversed(self)) else: key = next(iter(self)) value = self.pop(key) return key, value def __reduce__(self): items = [[k, self[k]] for k in self] tmp = self.__map, self.__end del self.__map, self.__end inst_dict = vars(self).copy() self.__map, self.__end = tmp if inst_dict: return self.__class__, (items,), inst_dict return self.__class__, (items,) def keys(self): return list(self) setdefault = DictMixin.setdefault update = DictMixin.update pop = DictMixin.pop values = DictMixin.values items = DictMixin.items def __repr__(self): if not self: return '%s()' % (self.__class__.__name__,) return '%s(%s)' % (self.__class__.__name__, list(self.items())) def copy(self): return self.__class__(self) # noinspection PyMethodOverriding @classmethod def fromkeys(cls, iterable, value=None): d = cls() for key in iterable: d[key] = value return d def __eq__(self, other): if isinstance(other, AttributedDict): return len(self) == len(other) and all( p == q for p, q in zip(sorted(self.items()), sorted(other.items()))) return dict.__eq__(self, other) def __ne__(self, other): return not self == other class AttributedList(list): def __init__(self): super(AttributedList, self).__init__() self.__attributes = [] def append(self, value, attributes=None): super(AttributedList, self).append(value) self.__attributes.append(attributes) def attributes(self, index): return self.__attributes[index]