diff options
author | Timothy Edmund Crosley <timothy.crosley@gmail.com> | 2015-09-14 20:58:18 -0700 |
---|---|---|
committer | Timothy Edmund Crosley <timothy.crosley@gmail.com> | 2015-09-14 20:58:18 -0700 |
commit | 54ed23ec8792219c9e6b34579f4b5733a87ce7ab (patch) | |
tree | bc3357d97cb065abfa95261792d570fdebd825e2 | |
parent | 8eba311cde95da8c01c934fc6363bce020ea5758 (diff) | |
parent | a37265b7bfecd42602d2594a04cf4b46f0f63a97 (diff) | |
download | pies-54ed23ec8792219c9e6b34579f4b5733a87ce7ab.tar.gz |
Merge pull request #53 from AbsoluteMSTR/patch-3
backport collection.ChainMap
-rw-r--r-- | pies/collections.py | 150 |
1 files changed, 149 insertions, 1 deletions
diff --git a/pies/collections.py b/pies/collections.py index a8a94cf..84c369a 100644 --- a/pies/collections.py +++ b/pies/collections.py @@ -1,5 +1,7 @@ from __future__ import absolute_import +import sys + from collections import * from .version_info import PY2 @@ -8,6 +10,152 @@ if PY2: from UserString import * from UserList import * - import sys if sys.version_info < (2, 7): from ordereddict import OrderedDict + +if sys.version_info < (3, 3): + if PY2: + try: + from thread import get_ident + except ImportError: + from dummy_thread import get_ident + else: + try: + from _thread import get_ident + except ImportError: + from _dummy_thread import get_ident + + def _recursive_repr(fillvalue='...'): + 'Decorator to make a repr function return fillvalue for a recursive call' + + def decorating_function(user_function): + repr_running = set() + + def wrapper(self): + key = id(self), get_ident() + if key in repr_running: + return fillvalue + repr_running.add(key) + try: + result = user_function(self) + finally: + repr_running.discard(key) + return result + + # Can't use functools.wraps() here because of bootstrap issues + wrapper.__module__ = getattr(user_function, '__module__') + wrapper.__doc__ = getattr(user_function, '__doc__') + wrapper.__name__ = getattr(user_function, '__name__') + wrapper.__annotations__ = getattr( + user_function, '__annotations__', {}) + return wrapper + + return decorating_function + + class ChainMap(MutableMapping): + ''' A ChainMap groups multiple dicts (or other mappings) together + to create a single, updateable view. + + The underlying mappings are stored in a list. That list is public and can + accessed or updated using the *maps* attribute. There is no other state. + + Lookups search the underlying mappings successively until a key is found. + In contrast, writes, updates, and deletions only operate on the first + mapping. + + ''' + + def __init__(self, *maps): + '''Initialize a ChainMap by setting *maps* to the given mappings. + If no mappings are provided, a single empty dictionary is used. + + ''' + self.maps = list(maps) or [{}] # always at least one map + + def __missing__(self, key): + raise KeyError(key) + + def __getitem__(self, key): + for mapping in self.maps: + try: + # can't use 'key in mapping' with defaultdict + return mapping[key] + except KeyError: + pass + # support subclasses that define __missing__ + return self.__missing__(key) + + def get(self, key, default=None): + return self[key] if key in self else default + + def __len__(self): + # reuses stored hash values if possible + return len(set().union(*self.maps)) + + def __iter__(self): + return iter(set().union(*self.maps)) + + def __contains__(self, key): + return any(key in m for m in self.maps) + + def __bool__(self): + return any(self.maps) + + @_recursive_repr() + def __repr__(self): + return '{0.__class__.__name__}({1})'.format( + self, ', '.join(map(repr, self.maps))) + + @classmethod + def fromkeys(cls, iterable, *args): + 'Create a ChainMap with a single dict created from the iterable.' + return cls(dict.fromkeys(iterable, *args)) + + def copy(self): + 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]' + return self.__class__(self.maps[0].copy(), *self.maps[1:]) + + __copy__ = copy + + def new_child(self, m=None): # like Django's Context.push() + ''' + New ChainMap with a new map followed by all previous maps. If no + map is provided, an empty dict is used. + ''' + if m is None: + m = {} + return self.__class__(m, *self.maps) + + @property + def parents(self): # like Django's Context.pop() + 'New ChainMap from maps[1:].' + return self.__class__(*self.maps[1:]) + + def __setitem__(self, key, value): + self.maps[0][key] = value + + def __delitem__(self, key): + try: + del self.maps[0][key] + except KeyError: + raise KeyError( + 'Key not found in the first mapping: {!r}'.format(key)) + + def popitem(self): + 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.' + try: + return self.maps[0].popitem() + except KeyError: + raise KeyError('No keys found in the first mapping.') + + def pop(self, key, *args): + 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].' + try: + return self.maps[0].pop(key, *args) + except KeyError: + raise KeyError( + 'Key not found in the first mapping: {!r}'.format(key)) + + def clear(self): + 'Clear maps[0], leaving maps[1:] intact.' + self.maps[0].clear() |