Source code for schlichtanders.mydicts

#!/usr/bin/python
# -*- coding: utf-8 -*-

""" This module mainly collects dictionary subclasses, which are useful to partially modify a dict.
But also a FrozenDict and a convenient ``update`` method for updating a dict with another, with options
to specify overwriting/appending flags.
"""

from __future__ import division
from .myfunctools import use_as_needed
from itertools import chain
from collections import Mapping, defaultdict

__author__ = 'Stephan Sahm <Stephan.Sahm@gmx.de>'


[docs]def update(dict1, dict2, overwrite=True, append=True): """ overwrites dict1 with dict2 """ if overwrite and append: dict1.update(dict2) if overwrite and not append: for k in dict1: if k in dict2: dict1[k] = dict2[k] if not overwrite and append: for k in dict2: if k not in dict1: dict1[k] = dict2[k] return dict1
[docs]def recdefaultdict(): """ this is an awesome little trick to have a dictionary with infinitely nested default dictionaries """ return defaultdict(recdefaultdict)
# fancy remappings =)
[docs]class ComposedDictsView(Mapping): """ composes two dictionaries with the given compose method """ COMPOSE_TYPES = ["stack", "call", 'use_as_needed', 'defaultto', 'overwrite'] def __init__(self, dict1, dict2, compose_type="stack", if_dict1_empty_iterate_over_dict2=False): """ constructs a view on a composition of dict1 and dict2 :param dict1: keys become keys of the view :param dict2: gets composed :param compose_type: Specifies method, how the values of the two dicts get composed. Defaults to 'stack'. =============== ====================================================== compose_type meaning =============== ====================================================== 'stack' self[key] = dict2[dict1[key]] 'call' self[key] = dict1[key](dict2) 'use_as_needed self[key] = use_as_needed(dict1[key], dict2) 'defaultto' self[key] = dict1[key] if key in dict1 else dict2[key] 'overwrite' self[key] = dict2[key] if key in dict2 else dict1[key] =============== ====================================================== :param if_dict1_empty_iterate_over_dict2: if True and dict1 is empty then the view's __iter__ method calls iter(dict2) instead """ self.dict1 = dict1 self.dict2 = dict2 self.compose_type = compose_type self.if_dict1_empty_iterate_over_dict2 = if_dict1_empty_iterate_over_dict2 def __getitem__(self, item): try: return getattr(self, "_getitem_%s" % self.compose_type)(item) except AttributeError: raise ValueError("`compose_type` %s is not supported. Should be e.g. one in [%s]." % (self.compose_type, ",".join(ComposedDictsView.COMPOSE_TYPES))) def _getitem_stack(self, item): return self.dict2[self.dict1[item]] def _getitem_call(self, item): return self.dict1[item](self.dict2) def _getitem_use_as_needed(self, item): return use_as_needed(self.dict1, self.dict2) def _getitem_defaultto(self, item): return self.dict1[item] if item in self.dict1 else self.dict2[item] def _getitem_overwrite(self, item): return self.dict2[item] if item in self.dict2 else self.dict1[item] def __len__(self): l = len(self.dict1) if l == 0 and self.if_dict1_empty_iterate_over_dict2: return len(self.dict2) else: return l def __iter__(self): if len(self.dict1) == 0 and self.if_dict1_empty_iterate_over_dict2: return iter(self.dict2) else: return iter(self.dict1)
[docs]class ModifyDict(Mapping): def __init__(self, base_dict, modify_getitem=lambda key, value: value): self.base_dict = base_dict self.modify_getitem = modify_getitem def __getitem__(self, item): return self.modify_getitem(item, self.base_dict[item]) def __iter__(self): return iter(self.base_dict) def __len__(self): return len(self.base_dict)
[docs]class DefaultDict(dict): """ like ``collections.defaultdict``, however default method is called with the accessed key """ def __init__(self, default_getitem=None, default_setitem=None, default_delitem=None, *args, **kwargs): """ constructs a DefaultDict :param default_getitem: defaults to returning the same key, i.e. like an identity function :param kwargs: kwargs for standard dict initializations """ # if either parameter is not callable, regard it as additional arg if default_delitem is not None and not hasattr(default_delitem, '__call__'): args = (default_delitem,) + args default_delitem = None if default_setitem is not None and not hasattr(default_getitem, '__call__'): args = (default_setitem,) + args default_setitem = None if default_getitem is not None and not hasattr(default_getitem, '__call__'): args = (default_getitem,) + args default_getitem = None self.default_getitem = super(DefaultDict, self).__getitem__ if default_getitem is None else default_getitem self.default_setitem = super(DefaultDict, self).__setitem__ if default_setitem is None else default_setitem self.default_delitem = super(DefaultDict, self).__delitem__ if default_delitem is None else default_delitem self.expand = True super(DefaultDict, self).__init__(*args, **kwargs)
[docs] def noexpand(self): self.expand = False return self
def __getitem__(self, key): try: return super(DefaultDict, self).__getitem__(key) except KeyError: value = self.default_getitem(key) if self.expand: super(DefaultDict, self).__setitem__(key, value) # super needed as setitem may be overwritten too return value def __setitem__(self, key, value): if key in self: super(DefaultDict, self).__setitem__(key, value) else: self.default_setitem(key, value) def __delitem__(self, key): if key in self: super(DefaultDict, self).__delitem__(key) else: self.default_delitem(key)
[docs]class IdentityDict(DefaultDict): """ almost like defaultdict, with the crucial difference that new keys are not added dynamically, but always regenerated """ def __init__(self, *args, **kwargs): """ see DefaultDict for signature """ super(IdentityDict, self).__init__(*args, **kwargs) self.expand = False
[docs] def set_expand(self): self.expand = True
[docs]class PassThroughDict(IdentityDict): """ postmap which passes everything through model if not further defined """ def __init__(self, dict_like, *args, **kwargs): super(PassThroughDict, self).__init__( dict_like.__getitem__, dict_like.__setitem__, dict_like.__delitem__, *args, **kwargs )
[docs]class HashableDict(dict): def __hash__(self): return id(self)
[docs]class FrozenDict(Mapping): # taken from http://stackoverflow.com/questions/2703599/what-would-a-frozen-dict-be """ This is a hashable wrapper around a dict interface """ def __init__(self, *args, **kwargs): self._d = dict(*args, **kwargs) self._hash = None def __iter__(self): return iter(self._d) def __len__(self): return len(self._d) def __getitem__(self, key): return self._d[key] def __hash__(self): # It would have been simpler and maybe more obvious to # use hash(tuple(sorted(self._d.iteritems()))) from this discussion # so far, but this solution is O(n). I don't know what kind of # n we are going to run into, but sometimes it's hard to resist the # urge to optimize when it will gain improved algorithmic performance. if self._hash is None: self._hash = 0 for pair in self.iteritems(): self._hash ^= hash(pair) return self._hash