Mercurial > repos > shellac > guppy_basecaller
diff env/lib/python3.7/site-packages/boltons/dictutils.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author | shellac |
---|---|
date | Mon, 01 Jun 2020 08:59:25 -0400 |
parents | 79f47841a781 |
children |
line wrap: on
line diff
--- a/env/lib/python3.7/site-packages/boltons/dictutils.py Thu May 14 16:47:39 2020 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1085 +0,0 @@ -# -*- coding: utf-8 -*- -"""Python has a very powerful mapping type at its core: the :class:`dict` -type. While versatile and featureful, the :class:`dict` prioritizes -simplicity and performance. As a result, it does not retain the order -of item insertion [1]_, nor does it store multiple values per key. It -is a fast, unordered 1:1 mapping. - -The :class:`OrderedMultiDict` contrasts to the built-in :class:`dict`, -as a relatively maximalist, ordered 1:n subtype of -:class:`dict`. Virtually every feature of :class:`dict` has been -retooled to be intuitive in the face of this added -complexity. Additional methods have been added, such as -:class:`collections.Counter`-like functionality. - -A prime advantage of the :class:`OrderedMultiDict` (OMD) is its -non-destructive nature. Data can be added to an :class:`OMD` without being -rearranged or overwritten. The property can allow the developer to -work more freely with the data, as well as make more assumptions about -where input data will end up in the output, all without any extra -work. - -One great example of this is the :meth:`OMD.inverted()` method, which -returns a new OMD with the values as keys and the keys as values. All -the data and the respective order is still represented in the inverted -form, all from an operation which would be outright wrong and reckless -with a built-in :class:`dict` or :class:`collections.OrderedDict`. - -The OMD has been performance tuned to be suitable for a wide range of -usages, including as a basic unordered MultiDict. Special -thanks to `Mark Williams`_ for all his help. - -.. [1] As of 2015, `basic dicts on PyPy are ordered - <http://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html>`_, - and as of December 2017, `basic dicts in CPython 3 are now ordered - <https://mail.python.org/pipermail/python-dev/2017-December/151283.html>`_, as - well. -.. _Mark Williams: https://github.com/markrwilliams - -""" - -try: - from collections.abc import KeysView, ValuesView, ItemsView -except ImportError: - from collections import KeysView, ValuesView, ItemsView - -import itertools - -try: - from itertools import izip_longest -except ImportError: - from itertools import zip_longest as izip_longest - -try: - from typeutils import make_sentinel - _MISSING = make_sentinel(var_name='_MISSING') -except ImportError: - _MISSING = object() - - -PREV, NEXT, KEY, VALUE, SPREV, SNEXT = range(6) - - -__all__ = ['MultiDict', 'OMD', 'OrderedMultiDict', 'OneToOne', 'ManyToMany', 'subdict', 'FrozenDict'] - -try: - profile -except NameError: - profile = lambda x: x - - -class OrderedMultiDict(dict): - """A MultiDict is a dictionary that can have multiple values per key - and the OrderedMultiDict (OMD) is a MultiDict that retains - original insertion order. Common use cases include: - - * handling query strings parsed from URLs - * inverting a dictionary to create a reverse index (values to keys) - * stacking data from multiple dictionaries in a non-destructive way - - The OrderedMultiDict constructor is identical to the built-in - :class:`dict`, and overall the API constitutes an intuitive - superset of the built-in type: - - >>> omd = OrderedMultiDict() - >>> omd['a'] = 1 - >>> omd['b'] = 2 - >>> omd.add('a', 3) - >>> omd.get('a') - 3 - >>> omd.getlist('a') - [1, 3] - - Some non-:class:`dict`-like behaviors also make an appearance, - such as support for :func:`reversed`: - - >>> list(reversed(omd)) - ['b', 'a'] - - Note that unlike some other MultiDicts, this OMD gives precedence - to the most recent value added. ``omd['a']`` refers to ``3``, not - ``1``. - - >>> omd - OrderedMultiDict([('a', 1), ('b', 2), ('a', 3)]) - >>> omd.poplast('a') - 3 - >>> omd - OrderedMultiDict([('a', 1), ('b', 2)]) - >>> omd.pop('a') - 1 - >>> omd - OrderedMultiDict([('b', 2)]) - - If you want a safe-to-modify or flat dictionary, use - :meth:`OrderedMultiDict.todict()`. - - >>> from pprint import pprint as pp # preserve printed ordering - >>> omd = OrderedMultiDict([('a', 1), ('b', 2), ('a', 3)]) - >>> pp(omd.todict()) - {'a': 3, 'b': 2} - >>> pp(omd.todict(multi=True)) - {'a': [1, 3], 'b': [2]} - - With ``multi=False``, items appear with the keys in to original - insertion order, alongside the most-recently inserted value for - that key. - - >>> OrderedMultiDict([('a', 1), ('b', 2), ('a', 3)]).items(multi=False) - [('a', 3), ('b', 2)] - - .. warning:: - - ``dict(omd)`` changed behavior `in Python 3.7 - <https://bugs.python.org/issue34320>`_ due to changes made to - support the transition from :class:`collections.OrderedDict` to - the built-in dictionary being ordered. Before 3.7, the result - would be a new dictionary, with values that were lists, similar - to ``omd.todict(multi=True)`` (but only shallow-copy; the lists - were direct references to OMD internal structures). From 3.7 - onward, the values became singular, like - ``omd.todict(multi=False)``. For reliable cross-version - behavior, just use :meth:`~OrderedMultiDict.todict()`. - - """ - def __init__(self, *args, **kwargs): - if len(args) > 1: - raise TypeError('%s expected at most 1 argument, got %s' - % (self.__class__.__name__, len(args))) - super(OrderedMultiDict, self).__init__() - - self._clear_ll() - if args: - self.update_extend(args[0]) - if kwargs: - self.update(kwargs) - - def _clear_ll(self): - try: - _map = self._map - except AttributeError: - _map = self._map = {} - self.root = [] - _map.clear() - self.root[:] = [self.root, self.root, None] - - def _insert(self, k, v): - root = self.root - cells = self._map.setdefault(k, []) - last = root[PREV] - cell = [last, root, k, v] - last[NEXT] = root[PREV] = cell - cells.append(cell) - - def add(self, k, v): - """Add a single value *v* under a key *k*. Existing values under *k* - are preserved. - """ - values = super(OrderedMultiDict, self).setdefault(k, []) - self._insert(k, v) - values.append(v) - - def addlist(self, k, v): - """Add an iterable of values underneath a specific key, preserving - any values already under that key. - - >>> omd = OrderedMultiDict([('a', -1)]) - >>> omd.addlist('a', range(3)) - >>> omd - OrderedMultiDict([('a', -1), ('a', 0), ('a', 1), ('a', 2)]) - - Called ``addlist`` for consistency with :meth:`getlist`, but - tuples and other sequences and iterables work. - """ - self_insert = self._insert - values = super(OrderedMultiDict, self).setdefault(k, []) - for subv in v: - self_insert(k, subv) - values.extend(v) - - def get(self, k, default=None): - """Return the value for key *k* if present in the dictionary, else - *default*. If *default* is not given, ``None`` is returned. - This method never raises a :exc:`KeyError`. - - To get all values under a key, use :meth:`OrderedMultiDict.getlist`. - """ - return super(OrderedMultiDict, self).get(k, [default])[-1] - - def getlist(self, k, default=_MISSING): - """Get all values for key *k* as a list, if *k* is in the - dictionary, else *default*. The list returned is a copy and - can be safely mutated. If *default* is not given, an empty - :class:`list` is returned. - """ - try: - return super(OrderedMultiDict, self).__getitem__(k)[:] - except KeyError: - if default is _MISSING: - return [] - return default - - def clear(self): - "Empty the dictionary." - super(OrderedMultiDict, self).clear() - self._clear_ll() - - def setdefault(self, k, default=_MISSING): - """If key *k* is in the dictionary, return its value. If not, insert - *k* with a value of *default* and return *default*. *default* - defaults to ``None``. See :meth:`dict.setdefault` for more - information. - """ - if not super(OrderedMultiDict, self).__contains__(k): - self[k] = None if default is _MISSING else default - return self[k] - - def copy(self): - "Return a shallow copy of the dictionary." - return self.__class__(self.iteritems(multi=True)) - - @classmethod - def fromkeys(cls, keys, default=None): - """Create a dictionary from a list of keys, with all the values - set to *default*, or ``None`` if *default* is not set. - """ - return cls([(k, default) for k in keys]) - - def update(self, E, **F): - """Add items from a dictionary or iterable (and/or keyword arguments), - overwriting values under an existing key. See - :meth:`dict.update` for more details. - """ - # E and F are throwback names to the dict() __doc__ - if E is self: - return - self_add = self.add - if isinstance(E, OrderedMultiDict): - for k in E: - if k in self: - del self[k] - for k, v in E.iteritems(multi=True): - self_add(k, v) - elif callable(getattr(E, 'keys', None)): - for k in E.keys(): - self[k] = E[k] - else: - seen = set() - seen_add = seen.add - for k, v in E: - if k not in seen and k in self: - del self[k] - seen_add(k) - self_add(k, v) - for k in F: - self[k] = F[k] - return - - def update_extend(self, E, **F): - """Add items from a dictionary, iterable, and/or keyword - arguments without overwriting existing items present in the - dictionary. Like :meth:`update`, but adds to existing keys - instead of overwriting them. - """ - if E is self: - iterator = iter(E.items()) - elif isinstance(E, OrderedMultiDict): - iterator = E.iteritems(multi=True) - elif hasattr(E, 'keys'): - iterator = ((k, E[k]) for k in E.keys()) - else: - iterator = E - - self_add = self.add - for k, v in iterator: - self_add(k, v) - - def __setitem__(self, k, v): - if super(OrderedMultiDict, self).__contains__(k): - self._remove_all(k) - self._insert(k, v) - super(OrderedMultiDict, self).__setitem__(k, [v]) - - def __getitem__(self, k): - return super(OrderedMultiDict, self).__getitem__(k)[-1] - - def __delitem__(self, k): - super(OrderedMultiDict, self).__delitem__(k) - self._remove_all(k) - - def __eq__(self, other): - if self is other: - return True - try: - if len(other) != len(self): - return False - except TypeError: - return False - if isinstance(other, OrderedMultiDict): - selfi = self.iteritems(multi=True) - otheri = other.iteritems(multi=True) - zipped_items = izip_longest(selfi, otheri, fillvalue=(None, None)) - for (selfk, selfv), (otherk, otherv) in zipped_items: - if selfk != otherk or selfv != otherv: - return False - if not(next(selfi, _MISSING) is _MISSING - and next(otheri, _MISSING) is _MISSING): - # leftovers (TODO: watch for StopIteration?) - return False - return True - elif hasattr(other, 'keys'): - for selfk in self: - try: - other[selfk] == self[selfk] - except KeyError: - return False - return True - return False - - def __ne__(self, other): - return not (self == other) - - def pop(self, k, default=_MISSING): - """Remove all values under key *k*, returning the most-recently - inserted value. Raises :exc:`KeyError` if the key is not - present and no *default* is provided. - """ - try: - return self.popall(k)[-1] - except KeyError: - if default is _MISSING: - raise KeyError(k) - return default - - def popall(self, k, default=_MISSING): - """Remove all values under key *k*, returning them in the form of - a list. Raises :exc:`KeyError` if the key is not present and no - *default* is provided. - """ - super_self = super(OrderedMultiDict, self) - if super_self.__contains__(k): - self._remove_all(k) - if default is _MISSING: - return super_self.pop(k) - return super_self.pop(k, default) - - def poplast(self, k=_MISSING, default=_MISSING): - """Remove and return the most-recently inserted value under the key - *k*, or the most-recently inserted key if *k* is not - provided. If no values remain under *k*, it will be removed - from the OMD. Raises :exc:`KeyError` if *k* is not present in - the dictionary, or the dictionary is empty. - """ - if k is _MISSING: - if self: - k = self.root[PREV][KEY] - else: - raise KeyError('empty %r' % type(self)) - try: - self._remove(k) - except KeyError: - if default is _MISSING: - raise KeyError(k) - return default - values = super(OrderedMultiDict, self).__getitem__(k) - v = values.pop() - if not values: - super(OrderedMultiDict, self).__delitem__(k) - return v - - def _remove(self, k): - values = self._map[k] - cell = values.pop() - cell[PREV][NEXT], cell[NEXT][PREV] = cell[NEXT], cell[PREV] - if not values: - del self._map[k] - - def _remove_all(self, k): - values = self._map[k] - while values: - cell = values.pop() - cell[PREV][NEXT], cell[NEXT][PREV] = cell[NEXT], cell[PREV] - del self._map[k] - - def iteritems(self, multi=False): - """Iterate over the OMD's items in insertion order. By default, - yields only the most-recently inserted value for each key. Set - *multi* to ``True`` to get all inserted items. - """ - root = self.root - curr = root[NEXT] - if multi: - while curr is not root: - yield curr[KEY], curr[VALUE] - curr = curr[NEXT] - else: - for key in self.iterkeys(): - yield key, self[key] - - def iterkeys(self, multi=False): - """Iterate over the OMD's keys in insertion order. By default, yields - each key once, according to the most recent insertion. Set - *multi* to ``True`` to get all keys, including duplicates, in - insertion order. - """ - root = self.root - curr = root[NEXT] - if multi: - while curr is not root: - yield curr[KEY] - curr = curr[NEXT] - else: - yielded = set() - yielded_add = yielded.add - while curr is not root: - k = curr[KEY] - if k not in yielded: - yielded_add(k) - yield k - curr = curr[NEXT] - - def itervalues(self, multi=False): - """Iterate over the OMD's values in insertion order. By default, - yields the most-recently inserted value per unique key. Set - *multi* to ``True`` to get all values according to insertion - order. - """ - for k, v in self.iteritems(multi=multi): - yield v - - def todict(self, multi=False): - """Gets a basic :class:`dict` of the items in this dictionary. Keys - are the same as the OMD, values are the most recently inserted - values for each key. - - Setting the *multi* arg to ``True`` is yields the same - result as calling :class:`dict` on the OMD, except that all the - value lists are copies that can be safely mutated. - """ - if multi: - return dict([(k, self.getlist(k)) for k in self]) - return dict([(k, self[k]) for k in self]) - - def sorted(self, key=None, reverse=False): - """Similar to the built-in :func:`sorted`, except this method returns - a new :class:`OrderedMultiDict` sorted by the provided key - function, optionally reversed. - - Args: - key (callable): A callable to determine the sort key of - each element. The callable should expect an **item** - (key-value pair tuple). - reverse (bool): Set to ``True`` to reverse the ordering. - - >>> omd = OrderedMultiDict(zip(range(3), range(3))) - >>> omd.sorted(reverse=True) - OrderedMultiDict([(2, 2), (1, 1), (0, 0)]) - - Note that the key function receives an **item** (key-value - tuple), so the recommended signature looks like: - - >>> omd = OrderedMultiDict(zip('hello', 'world')) - >>> omd.sorted(key=lambda i: i[1]) # i[0] is the key, i[1] is the val - OrderedMultiDict([('o', 'd'), ('l', 'l'), ('e', 'o'), ('l', 'r'), ('h', 'w')]) - """ - cls = self.__class__ - return cls(sorted(self.iteritems(multi=True), key=key, reverse=reverse)) - - def sortedvalues(self, key=None, reverse=False): - """Returns a copy of the :class:`OrderedMultiDict` with the same keys - in the same order as the original OMD, but the values within - each keyspace have been sorted according to *key* and - *reverse*. - - Args: - key (callable): A single-argument callable to determine - the sort key of each element. The callable should expect - an **item** (key-value pair tuple). - reverse (bool): Set to ``True`` to reverse the ordering. - - >>> omd = OrderedMultiDict() - >>> omd.addlist('even', [6, 2]) - >>> omd.addlist('odd', [1, 5]) - >>> omd.add('even', 4) - >>> omd.add('odd', 3) - >>> somd = omd.sortedvalues() - >>> somd.getlist('even') - [2, 4, 6] - >>> somd.keys(multi=True) == omd.keys(multi=True) - True - >>> omd == somd - False - >>> somd - OrderedMultiDict([('even', 2), ('even', 4), ('odd', 1), ('odd', 3), ('even', 6), ('odd', 5)]) - - As demonstrated above, contents and key order are - retained. Only value order changes. - """ - try: - superself_iteritems = super(OrderedMultiDict, self).iteritems() - except AttributeError: - superself_iteritems = super(OrderedMultiDict, self).items() - # (not reverse) because they pop off in reverse order for reinsertion - sorted_val_map = dict([(k, sorted(v, key=key, reverse=(not reverse))) - for k, v in superself_iteritems]) - ret = self.__class__() - for k in self.iterkeys(multi=True): - ret.add(k, sorted_val_map[k].pop()) - return ret - - def inverted(self): - """Returns a new :class:`OrderedMultiDict` with values and keys - swapped, like creating dictionary transposition or reverse - index. Insertion order is retained and all keys and values - are represented in the output. - - >>> omd = OMD([(0, 2), (1, 2)]) - >>> omd.inverted().getlist(2) - [0, 1] - - Inverting twice yields a copy of the original: - - >>> omd.inverted().inverted() - OrderedMultiDict([(0, 2), (1, 2)]) - """ - return self.__class__((v, k) for k, v in self.iteritems(multi=True)) - - def counts(self): - """Returns a mapping from key to number of values inserted under that - key. Like :py:class:`collections.Counter`, but returns a new - :class:`OrderedMultiDict`. - """ - # Returns an OMD because Counter/OrderedDict may not be - # available, and neither Counter nor dict maintain order. - super_getitem = super(OrderedMultiDict, self).__getitem__ - return self.__class__((k, len(super_getitem(k))) for k in self) - - def keys(self, multi=False): - """Returns a list containing the output of :meth:`iterkeys`. See - that method's docs for more details. - """ - return list(self.iterkeys(multi=multi)) - - def values(self, multi=False): - """Returns a list containing the output of :meth:`itervalues`. See - that method's docs for more details. - """ - return list(self.itervalues(multi=multi)) - - def items(self, multi=False): - """Returns a list containing the output of :meth:`iteritems`. See - that method's docs for more details. - """ - return list(self.iteritems(multi=multi)) - - def __iter__(self): - return self.iterkeys() - - def __reversed__(self): - root = self.root - curr = root[PREV] - lengths = {} - lengths_sd = lengths.setdefault - get_values = super(OrderedMultiDict, self).__getitem__ - while curr is not root: - k = curr[KEY] - vals = get_values(k) - if lengths_sd(k, 1) == len(vals): - yield k - lengths[k] += 1 - curr = curr[PREV] - - def __repr__(self): - cn = self.__class__.__name__ - kvs = ', '.join([repr((k, v)) for k, v in self.iteritems(multi=True)]) - return '%s([%s])' % (cn, kvs) - - def viewkeys(self): - "OMD.viewkeys() -> a set-like object providing a view on OMD's keys" - return KeysView(self) - - def viewvalues(self): - "OMD.viewvalues() -> an object providing a view on OMD's values" - return ValuesView(self) - - def viewitems(self): - "OMD.viewitems() -> a set-like object providing a view on OMD's items" - return ItemsView(self) - - -# A couple of convenient aliases -OMD = OrderedMultiDict -MultiDict = OrderedMultiDict - - -class FastIterOrderedMultiDict(OrderedMultiDict): - """An OrderedMultiDict backed by a skip list. Iteration over keys - is faster and uses constant memory but adding duplicate key-value - pairs is slower. Brainchild of Mark Williams. - """ - def _clear_ll(self): - # TODO: always reset objects? (i.e., no else block below) - try: - _map = self._map - except AttributeError: - _map = self._map = {} - self.root = [] - _map.clear() - self.root[:] = [self.root, self.root, - None, None, - self.root, self.root] - - def _insert(self, k, v): - root = self.root - empty = [] - cells = self._map.setdefault(k, empty) - last = root[PREV] - - if cells is empty: - cell = [last, root, - k, v, - last, root] - # was the last one skipped? - if last[SPREV][SNEXT] is root: - last[SPREV][SNEXT] = cell - last[NEXT] = last[SNEXT] = root[PREV] = root[SPREV] = cell - cells.append(cell) - else: - # if the previous was skipped, go back to the cell that - # skipped it - sprev = last[SPREV] if (last[SPREV][SNEXT] is not last) else last - cell = [last, root, - k, v, - sprev, root] - # skip me - last[SNEXT] = root - last[NEXT] = root[PREV] = root[SPREV] = cell - cells.append(cell) - - def _remove(self, k): - cells = self._map[k] - cell = cells.pop() - if not cells: - del self._map[k] - cell[PREV][SNEXT] = cell[SNEXT] - - if cell[PREV][SPREV][SNEXT] is cell: - cell[PREV][SPREV][SNEXT] = cell[NEXT] - elif cell[SNEXT] is cell[NEXT]: - cell[SPREV][SNEXT], cell[SNEXT][SPREV] = cell[SNEXT], cell[SPREV] - - cell[PREV][NEXT], cell[NEXT][PREV] = cell[NEXT], cell[PREV] - - def _remove_all(self, k): - cells = self._map.pop(k) - while cells: - cell = cells.pop() - if cell[PREV][SPREV][SNEXT] is cell: - cell[PREV][SPREV][SNEXT] = cell[NEXT] - elif cell[SNEXT] is cell[NEXT]: - cell[SPREV][SNEXT], cell[SNEXT][SPREV] = cell[SNEXT], cell[SPREV] - - cell[PREV][NEXT], cell[NEXT][PREV] = cell[NEXT], cell[PREV] - cell[PREV][SNEXT] = cell[SNEXT] - - def iteritems(self, multi=False): - next_link = NEXT if multi else SNEXT - root = self.root - curr = root[next_link] - while curr is not root: - yield curr[KEY], curr[VALUE] - curr = curr[next_link] - - def iterkeys(self, multi=False): - next_link = NEXT if multi else SNEXT - root = self.root - curr = root[next_link] - while curr is not root: - yield curr[KEY] - curr = curr[next_link] - - def __reversed__(self): - root = self.root - curr = root[PREV] - while curr is not root: - if curr[SPREV][SNEXT] is not curr: - curr = curr[SPREV] - if curr is root: - break - yield curr[KEY] - curr = curr[PREV] - - -_OTO_INV_MARKER = object() -_OTO_UNIQUE_MARKER = object() - - -class OneToOne(dict): - """Implements a one-to-one mapping dictionary. In addition to - inheriting from and behaving exactly like the builtin - :class:`dict`, all values are automatically added as keys on a - reverse mapping, available as the `inv` attribute. This - arrangement keeps key and value namespaces distinct. - - Basic operations are intuitive: - - >>> oto = OneToOne({'a': 1, 'b': 2}) - >>> print(oto['a']) - 1 - >>> print(oto.inv[1]) - a - >>> len(oto) - 2 - - Overwrites happens in both directions: - - >>> oto.inv[1] = 'c' - >>> print(oto.get('a')) - None - >>> len(oto) - 2 - - For a very similar project, with even more one-to-one - functionality, check out `bidict <https://github.com/jab/bidict>`_. - """ - __slots__ = ('inv',) - - def __init__(self, *a, **kw): - raise_on_dupe = False - if a: - if a[0] is _OTO_INV_MARKER: - self.inv = a[1] - dict.__init__(self, [(v, k) for k, v in self.inv.items()]) - return - elif a[0] is _OTO_UNIQUE_MARKER: - a, raise_on_dupe = a[1:], True - - dict.__init__(self, *a, **kw) - self.inv = self.__class__(_OTO_INV_MARKER, self) - - if len(self) == len(self.inv): - # if lengths match, that means everything's unique - return - - if not raise_on_dupe: - dict.clear(self) - dict.update(self, [(v, k) for k, v in self.inv.items()]) - return - - # generate an error message if the values aren't 1:1 - - val_multidict = {} - for k, v in self.items(): - val_multidict.setdefault(v, []).append(k) - - dupes = dict([(v, k_list) for v, k_list in - val_multidict.items() if len(k_list) > 1]) - - raise ValueError('expected unique values, got multiple keys for' - ' the following values: %r' % dupes) - - @classmethod - def unique(cls, *a, **kw): - """This alternate constructor for OneToOne will raise an exception - when input values overlap. For instance: - - >>> OneToOne.unique({'a': 1, 'b': 1}) - Traceback (most recent call last): - ... - ValueError: expected unique values, got multiple keys for the following values: ... - - This even works across inputs: - - >>> a_dict = {'a': 2} - >>> OneToOne.unique(a_dict, b=2) - Traceback (most recent call last): - ... - ValueError: expected unique values, got multiple keys for the following values: ... - """ - return cls(_OTO_UNIQUE_MARKER, *a, **kw) - - def __setitem__(self, key, val): - hash(val) # ensure val is a valid key - if key in self: - dict.__delitem__(self.inv, self[key]) - if val in self.inv: - del self.inv[val] - dict.__setitem__(self, key, val) - dict.__setitem__(self.inv, val, key) - - def __delitem__(self, key): - dict.__delitem__(self.inv, self[key]) - dict.__delitem__(self, key) - - def clear(self): - dict.clear(self) - dict.clear(self.inv) - - def copy(self): - return self.__class__(self) - - def pop(self, key, default=_MISSING): - if key in self: - dict.__delitem__(self.inv, self[key]) - return dict.pop(self, key) - if default is not _MISSING: - return default - raise KeyError() - - def popitem(self): - key, val = dict.popitem(self) - dict.__delitem__(self.inv, val) - return key, val - - def setdefault(self, key, default=None): - if key not in self: - self[key] = default - return self[key] - - def update(self, dict_or_iterable, **kw): - if isinstance(dict_or_iterable, dict): - for val in dict_or_iterable.values(): - hash(val) - keys_vals = list(dict_or_iterable.items()) - else: - for key, val in dict_or_iterable: - hash(key) - hash(val) - keys_vals = list(dict_or_iterable) - for val in kw.values(): - hash(val) - keys_vals.extend(kw.items()) - for key, val in keys_vals: - self[key] = val - - def __repr__(self): - cn = self.__class__.__name__ - dict_repr = dict.__repr__(self) - return "%s(%s)" % (cn, dict_repr) - - -# marker for the secret handshake used internally to set up the invert ManyToMany -_PAIRING = object() - - -class ManyToMany(object): - """ - a dict-like entity that represents a many-to-many relationship - between two groups of objects - - behaves like a dict-of-tuples; also has .inv which is kept - up to date which is a dict-of-tuples in the other direction - - also, can be used as a directed graph among hashable python objects - """ - def __init__(self, items=None): - self.data = {} - if type(items) is tuple and items and items[0] is _PAIRING: - self.inv = items[1] - else: - self.inv = self.__class__((_PAIRING, self)) - if items: - self.update(items) - return - - def get(self, key, default=frozenset()): - try: - return self[key] - except KeyError: - return default - - def __getitem__(self, key): - return frozenset(self.data[key]) - - def __setitem__(self, key, vals): - vals = set(vals) - if key in self: - to_remove = self.data[key] - vals - vals -= self.data[key] - for val in to_remove: - self.remove(key, val) - for val in vals: - self.add(key, val) - - def __delitem__(self, key): - for val in self.data.pop(key): - self.inv.data[val].remove(key) - if not self.inv.data[val]: - del self.inv.data[val] - - def update(self, iterable): - """given an iterable of (key, val), add them all""" - if type(iterable) is type(self): - other = iterable - for k in other.data: - if k not in self.data: - self.data[k] = other.data[k] - else: - self.data[k].update(other.data[k]) - for k in other.inv.data: - if k not in self.inv.data: - self.inv.data[k] = other.inv.data[k] - else: - self.inv.data[k].update(other.inv.data[k]) - elif callable(getattr(iterable, 'keys', None)): - for k in iterable.keys(): - self.add(k, iterable[k]) - else: - for key, val in iterable: - self.add(key, val) - return - - def add(self, key, val): - if key not in self.data: - self.data[key] = set() - self.data[key].add(val) - if val not in self.inv.data: - self.inv.data[val] = set() - self.inv.data[val].add(key) - - def remove(self, key, val): - self.data[key].remove(val) - if not self.data[key]: - del self.data[key] - self.inv.data[val].remove(key) - if not self.inv.data[val]: - del self.inv.data[val] - - def replace(self, key, newkey): - """ - replace instances of key by newkey - """ - if key not in self.data: - return - self.data[newkey] = fwdset = self.data.pop(key) - for val in fwdset: - revset = self.inv.data[val] - revset.remove(key) - revset.add(newkey) - - def iteritems(self): - for key in self.data: - for val in self.data[key]: - yield key, val - - def keys(self): - return self.data.keys() - - def __contains__(self, key): - return key in self.data - - def __iter__(self): - return self.data.__iter__() - - def __len__(self): - return self.data.__len__() - - def __eq__(self, other): - return type(self) == type(other) and self.data == other.data - - def __repr__(self): - cn = self.__class__.__name__ - return '%s(%r)' % (cn, list(self.iteritems())) - - -def subdict(d, keep=None, drop=None): - """Compute the "subdictionary" of a dict, *d*. - - A subdict is to a dict what a subset is a to set. If *A* is a - subdict of *B*, that means that all keys of *A* are present in - *B*. - - Returns a new dict with any keys in *drop* removed, and any keys - in *keep* still present, provided they were in the original - dict. *keep* defaults to all keys, *drop* defaults to empty, so - without one of these arguments, calling this function is - equivalent to calling ``dict()``. - - >>> from pprint import pprint as pp - >>> pp(subdict({'a': 1, 'b': 2})) - {'a': 1, 'b': 2} - >>> subdict({'a': 1, 'b': 2, 'c': 3}, drop=['b', 'c']) - {'a': 1} - >>> pp(subdict({'a': 1, 'b': 2, 'c': 3}, keep=['a', 'c'])) - {'a': 1, 'c': 3} - - """ - if keep is None: - keep = d.keys() - if drop is None: - drop = [] - - keys = set(keep) - set(drop) - - return type(d)([(k, v) for k, v in d.items() if k in keys]) - - -class FrozenHashError(TypeError): - pass - - -class FrozenDict(dict): - """An immutable dict subtype that is hashable and can itself be used - as a :class:`dict` key or :class:`set` entry. What - :class:`frozenset` is to :class:`set`, FrozenDict is to - :class:`dict`. - - There was once an attempt to introduce such a type to the standard - library, but it was rejected: `PEP 416 <https://www.python.org/dev/peps/pep-0416/>`_. - - Because FrozenDict is a :class:`dict` subtype, it automatically - works everywhere a dict would, including JSON serialization. - - """ - __slots__ = ('_hash',) - - def updated(self, *a, **kw): - """Make a copy and add items from a dictionary or iterable (and/or - keyword arguments), overwriting values under an existing - key. See :meth:`dict.update` for more details. - """ - data = dict(self) - data.update(*a, **kw) - return type(self)(data) - - @classmethod - def fromkeys(cls, keys, value=None): - # one of the lesser known and used/useful dict methods - return cls(dict.fromkeys(keys, value)) - - def __repr__(self): - cn = self.__class__.__name__ - return '%s(%s)' % (cn, dict.__repr__(self)) - - def __reduce_ex__(self, protocol): - return type(self), (dict(self),) - - def __hash__(self): - try: - ret = self._hash - except AttributeError: - try: - ret = self._hash = hash(frozenset(self.items())) - except Exception as e: - ret = self._hash = FrozenHashError(e) - - if ret.__class__ is FrozenHashError: - raise ret - - return ret - - def __copy__(self): - return self # immutable types don't copy, see tuple's behavior - - # block everything else - def _raise_frozen_typeerror(self, *a, **kw): - "raises a TypeError, because FrozenDicts are immutable" - raise TypeError('%s object is immutable' % self.__class__.__name__) - - __setitem__ = __delitem__ = update = _raise_frozen_typeerror - setdefault = pop = popitem = clear = _raise_frozen_typeerror - - del _raise_frozen_typeerror - - -# end dictutils.py