# Copyright: 2005-2009 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # Author(s): Brian Harring (ferringb@gentoo.org) __all__ = ["Mapping", "MutableMapping", "UserDict", "ProtectedDict", "LazyLoad", "slot_dict_class"] import sys import weakref class Mapping(object): """ In python-3.0, the UserDict.DictMixin class has been replaced by Mapping and MutableMapping from the collections module, but 2to3 doesn't currently account for this change: http://bugs.python.org/issue2876 As a workaround for the above issue, use this class as a substitute for UserDict.DictMixin so that code converted via 2to3 will run. """ __slots__ = () def __iter__(self): return iter(self.keys()) def keys(self): return list(self.__iter__()) def __contains__(self, key): try: value = self[key] except KeyError: return False return True def iteritems(self): for k in self: yield (k, self[k]) def iterkeys(self): return self.__iter__() def itervalues(self): for _, v in self.items(): yield v def values(self): return [v for _, v in self.iteritems()] def items(self): return list(self.iteritems()) def get(self, key, default=None): try: return self[key] except KeyError: return default def __repr__(self): return repr(dict(self.items())) def __len__(self): return len(list(self)) if sys.hexversion >= 0x3000000: items = iteritems keys = __iter__ values = itervalues class MutableMapping(Mapping): """ A mutable vesion of the Mapping class. """ __slots__ = () def clear(self): for key in list(self): del self[key] def setdefault(self, key, default=None): try: return self[key] except KeyError: self[key] = default return default def pop(self, key, *args): if len(args) > 1: raise TypeError("pop expected at most 2 arguments, got " + \ repr(1 + len(args))) try: value = self[key] except KeyError: if args: return args[0] raise del self[key] return value def popitem(self): try: k, v = next(iter(self.items())) except StopIteration: raise KeyError('container is empty') del self[k] return (k, v) def update(self, *args, **kwargs): if len(args) > 1: raise TypeError( "expected at most 1 positional argument, got " + \ repr(len(args))) other = None if args: other = args[0] if other is None: pass elif hasattr(other, 'iteritems'): # Use getattr to avoid interference from 2to3. for k, v in getattr(other, 'iteritems')(): self[k] = v elif hasattr(other, 'items'): # Use getattr to avoid interference from 2to3. for k, v in getattr(other, 'items')(): self[k] = v elif hasattr(other, 'keys'): for k in other.keys(): self[k] = other[k] else: for k, v in other: self[k] = v if kwargs: self.update(kwargs) class UserDict(MutableMapping): """ Use this class as a substitute for UserDict.UserDict so that code converted via 2to3 will run: http://bugs.python.org/issue2876 """ __slots__ = ('data',) def __init__(self, *args, **kwargs): self.data = {} if len(args) > 1: raise TypeError( "expected at most 1 positional argument, got " + \ repr(len(args))) if args: self.update(args[0]) if kwargs: self.update(kwargs) def __repr__(self): return repr(self.data) def __contains__(self, key): return key in self.data def __iter__(self): return iter(self.data) def __len__(self): return len(self.data) def __getitem__(self, key): return self.data[key] def __setitem__(self, key, item): self.data[key] = item def __delitem__(self, key): del self.data[key] def clear(self): self.data.clear() if sys.hexversion >= 0x3000000: keys = __iter__ class OrderedDict(UserDict): __slots__ = ('_order',) def __init__(self, *args, **kwargs): self._order = [] UserDict.__init__(self, *args, **kwargs) def __iter__(self): return iter(self._order) def __setitem__(self, key, item): if key in self: self._order.remove(key) UserDict.__setitem__(self, key, item) self._order.append(key) def __delitem__(self, key): UserDict.__delitem__(self, key) self._order.remove(key) def clear(self): UserDict.clear(self) del self._order[:] if sys.hexversion >= 0x3000000: keys = __iter__ class ProtectedDict(MutableMapping): """ given an initial dict, this wraps that dict storing changes in a secondary dict, protecting the underlying dict from changes """ __slots__=("orig","new","blacklist") def __init__(self, orig): self.orig = orig self.new = {} self.blacklist = {} def __setitem__(self, key, val): self.new[key] = val if key in self.blacklist: del self.blacklist[key] def __getitem__(self, key): if key in self.new: return self.new[key] if key in self.blacklist: raise KeyError(key) return self.orig[key] def __delitem__(self, key): if key in self.new: del self.new[key] elif key in self.orig: if key not in self.blacklist: self.blacklist[key] = True return raise KeyError(key) def __iter__(self): for k in self.new: yield k for k in self.orig: if k not in self.blacklist and k not in self.new: yield k def __contains__(self, key): return key in self.new or (key not in self.blacklist and key in self.orig) if sys.hexversion >= 0x3000000: keys = __iter__ class LazyLoad(Mapping): """ Lazy loading of values for a dict """ __slots__=("pull", "d") def __init__(self, pull_items_func, initial_items=[]): self.d = {} for k, v in initial_items: self.d[k] = v self.pull = pull_items_func def __getitem__(self, key): if key in self.d: return self.d[key] elif self.pull != None: self.d.update(self.pull()) self.pull = None return self.d[key] def __iter__(self): if self.pull is not None: self.d.update(self.pull()) self.pull = None return iter(self.d) def __contains__(self, key): if key in self.d: return True elif self.pull != None: self.d.update(self.pull()) self.pull = None return key in self.d if sys.hexversion >= 0x3000000: keys = __iter__ _slot_dict_classes = weakref.WeakValueDictionary() def slot_dict_class(keys, prefix="_val_"): """ Generates mapping classes that behave similar to a dict but store values as object attributes that are allocated via __slots__. Instances of these objects have a smaller memory footprint than a normal dict object. @param keys: Fixed set of allowed keys @type keys: Iterable @param prefix: a prefix to use when mapping attribute names from keys @type prefix: String @rtype: SlotDict @return: A class that constructs SlotDict instances having the specified keys. """ if isinstance(keys, frozenset): keys_set = keys else: keys_set = frozenset(keys) v = _slot_dict_classes.get((keys_set, prefix)) if v is None: class SlotDict(object): allowed_keys = keys_set _prefix = prefix __slots__ = ("__weakref__",) + \ tuple(prefix + k for k in allowed_keys) def __init__(self, *args, **kwargs): if len(args) > 1: raise TypeError( "expected at most 1 positional argument, got " + \ repr(len(args))) if args: self.update(args[0]) if kwargs: self.update(kwargs) def __iter__(self): for k, v in self.iteritems(): yield k def __len__(self): l = 0 for i in self.iteritems(): l += 1 return l def keys(self): return list(self) def iteritems(self): prefix = self._prefix for k in self.allowed_keys: try: yield (k, getattr(self, prefix + k)) except AttributeError: pass def items(self): return list(self.iteritems()) def itervalues(self): for k, v in self.iteritems(): yield v def values(self): return list(self.itervalues()) def __delitem__(self, k): try: delattr(self, self._prefix + k) except AttributeError: raise KeyError(k) def __setitem__(self, k, v): setattr(self, self._prefix + k, v) def setdefault(self, key, default=None): try: return self[key] except KeyError: self[key] = default return default def update(self, *args, **kwargs): if len(args) > 1: raise TypeError( "expected at most 1 positional argument, got " + \ repr(len(args))) other = None if args: other = args[0] if other is None: pass elif hasattr(other, 'iteritems'): # Use getattr to avoid interference from 2to3. for k, v in getattr(other, 'iteritems')(): self[k] = v elif hasattr(other, 'items'): # Use getattr to avoid interference from 2to3. for k, v in getattr(other, 'items')(): self[k] = v elif hasattr(other, 'keys'): for k in other.keys(): self[k] = other[k] else: for k, v in other: self[k] = v if kwargs: self.update(kwargs) def __getitem__(self, k): try: return getattr(self, self._prefix + k) except AttributeError: raise KeyError(k) def get(self, key, default=None): try: return self[key] except KeyError: return default def __contains__(self, k): return hasattr(self, self._prefix + k) def pop(self, key, *args): if len(args) > 1: raise TypeError( "pop expected at most 2 arguments, got " + \ repr(1 + len(args))) try: value = self[key] except KeyError: if args: return args[0] raise del self[key] return value def popitem(self): try: k, v = self.iteritems().next() except StopIteration: raise KeyError('container is empty') del self[k] return (k, v) def copy(self): c = self.__class__() c.update(self) return c def clear(self): for k in self.allowed_keys: try: delattr(self, self._prefix + k) except AttributeError: pass def __str__(self): return str(dict(self.iteritems())) def __repr__(self): return repr(dict(self.iteritems())) if sys.hexversion >= 0x3000000: items = iteritems keys = __iter__ values = itervalues v = SlotDict _slot_dict_classes[v.allowed_keys] = v return v