Skip to content

Instantly share code, notes, and snippets.

@metaist
Created April 14, 2016 02:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save metaist/13640b4ea7cdb3fa501b86a709b22e5e to your computer and use it in GitHub Desktop.
Save metaist/13640b4ea7cdb3fa501b86a709b22e5e to your computer and use it in GitHub Desktop.
Manage flexible dicts in python.
#!/usr/bin/env python
# coding: utf-8
'''Manage namespaces.'''
# Native
import collections
# <dict> shorthand
_del = dict.__delitem__
_get = dict.__getitem__
_in = dict.__contains__
_set = dict.__setitem__
_getattr = object.__getattribute__
_setattr = object.__setattr__
def ismap(item):
'''Return True if item is a mapping.
Args:
item (any): item to check
Returns:
(bool): True if item is a Mapping, False otherwise.
'''
return isinstance(item, collections.Mapping)
def byteify(item):
'''Return str instead of unicode.
Args:
item (any): item to encode
Returns:
(any): all of the unicode items replaced with strings
Examples:
>>> byteify(u'test') == 'test'
True
>>> byteify([u'a', u'b', 'c']) == ['a', 'b', 'c']
True
>>> byteify({u'd': {u'e': 'f'}}) == {'d': {'e': 'f'}}
True
'''
if ismap(item):
return {byteify(key): byteify(value)
for key, value in item.iteritems()}
elif isinstance(item, list):
return [byteify(element) for element in item]
elif isinstance(item, unicode):
return item.encode('utf-8')
else:
return item
def dictify(item):
'''Return a dict version of a Mapping.
Args:
item (collections.Mapping): item to convert
Result:
(dict): all deeper namespaces converted to dicts
'''
result = {}
for k, v in item.items():
if isinstance(v, NS):
result[k] = dictify(v)
else:
result[k] = v
return result
def namespaceify(item, delimeter=':'):
'''Return a namespaced version of a Mapping.
Args:
item (collections.Mapping): item to convert
delimeter (str): namespace delimeter (default: ':')
Returns:
(NS): namespaced version of item
'''
result = NS()
result.delimeter = delimeter
for k, v in item.items():
if not isinstance(v, NS) and ismap(v):
result[k] = namespaceify(v, delimeter)
else:
result[k] = v
return result
def merge(first, *others):
'''Return a dictionary in which the keys are merged.
Args:
first (dict): the initial dictionary to merge
*others (dict...): additional dictionaries to merge
Return:
(dict): the results of the merge
'''
result = first
for item in others:
if not item:
continue
for key, next_val in item.items():
prev_val = result.get(key)
prev_type, next_type = type(prev_val), type(next_val)
if prev_type == next_type and prev_val == next_val:
continue # already set to the correct value / type
if ismap(prev_val) and ismap(next_val):
result[key] = merge(prev_val, next_val)
else:
result[key] = next_val
return result
class AttrDict(dict):
'''Flexible dict with attribute access.'''
def __setattr__(self, name, value):
'''Set the value of an attribute.
Args:
name (str): name of the attribute
value (any): value to set
Examples:
>>> item = AttrDict()
>>> item.a = 1
>>> item['a'] == 1
True
'''
try:
_getattr(self, name)
_setattr(self, name, value)
except AttributeError:
self[name] = value
def __getattr__(self, name):
'''Return the value of the attribute.
Args:
name (str): name of the attribute
Returns:
(any): value of the attribute, or None if it is missing
Examples:
>>> item = AttrDict(a=1)
>>> item.a == 1
True
'''
return self[name]
def __delattr__(self, name):
'''Delete the attribute.
Args:
name (str): name of the attribute
Examples:
>>> item = AttrDict(a=1, b=2)
>>> del item.a
>>> item == {'b': 2}
True
'''
del self[name]
def __setitem__(self, name, value):
'''Set the value of a key.
Args:
name (str): name of the key
value (any): value to set
Examples:
>>> item = AttrDict()
>>> item['a'] = 1
>>> item.a == 1
True
'''
_set(self, name, value)
def __getitem__(self, name):
'''Return the value of the key.
Args:
name (str): name of the key
Returns:
(any): value of the key, or None if it is missing
Examples:
>>> item = AttrDict(a=1)
>>> item['a'] == 1
True
'''
result = None
if _in(self, name):
result = _get(self, name)
return result
def __delitem__(self, name):
'''Delete a key.
Args:
name (str): name of the key
Examples:
>>> item = AttrDict(a=1, b=2)
>>> del item['a']
>>> item == {'b': 2}
True
'''
_del(self, name)
def __iadd__(self, other):
'''Add another object to this object in place.
Args:
other (collections.Mapping): object to add
Examples:
>>> item = AttrDict(a=1)
>>> item += {'b': {'c': 3}}
>>> item == {'a': 1, 'b': {'c': 3}}
True
'''
merge(self, other)
return self
def __add__(self, other):
'''Add another object to this object.
Args:
other (collections.Mapping): object to add
Returns:
(AttrDict): new instance
Examples:
>>> item = AttrDict(a=1) + {'b': 2}
>>> item == AttrDict(a=1, b=2)
True
'''
return AttrDict(merge({}, self, other))
def __radd__(self, other):
'''Add this object to another object.
Returns:
(AttrDict): new instance
Args:
other (collections.Mapping): object to add
Examples:
>>> item = {'b': 2} + AttrDict(a=1)
>>> item == AttrDict(a=1, b=2)
True
'''
return AttrDict(merge({}, other, self))
class NS(AttrDict):
'''A general-purpose dict-like namespace.'''
delimeter = ':'
def __init__(self, *args, **kwds):
'''Construct a namespace from parameters.
Args:
*args: dictionaries to merge
**kwds: keyword arguments to convert into a dictionary
Examples:
>>> ns = NS()
>>> ns == {}
True
>>> ns = NS({'a': 1}, {'b': 2}, {'c': 3})
>>> ns == {'a': 1, 'b': 2, 'c': 3}
True
'''
# pylint: disable=super-init-not-called
args = list(args)
args.append(kwds)
for arg in args:
if not arg: # ignore empty
continue
if not ismap(arg):
raise TypeError('[{0}] cannot be merged'.format(arg))
merge(self, arg)
def __contains__(self, key):
'''Returns True if key is in the Namespace.
Args:
key (str): dotted name of the key
Returns:
(bool): True if the name is in the namespace; False otherwise
Examples:
>>> ns = NS(a=1, b=None, d={'e': {'f': 5}})
>>> 'a' in ns
True
>>> 'b' in ns
False
>>> 'c' in ns
False
>>> 'd:e:f' in ns
True
'''
result = True
obj = self
parts = key.split(self.delimeter)
for part in parts:
if ismap(obj) and _in(obj, part):
obj = _get(obj, part)
else:
result = False
break
return result
def __delitem__(self, key):
'''Remove a key from the object.
Args:
key (str): key to remove
Examples:
>>> ns = NS(a={'b': 3})
>>> ns['a:b'] == 3
True
>>> del ns['a:b']
>>> ns['a:b'] is None
True
'''
obj = self
parts = key.split(self.delimeter)
last = parts.pop()
for part in parts:
if ismap(obj) and _in(obj, part):
obj = _get(obj, part)
else:
raise KeyError(key)
_del(obj, last)
def __getitem__(self, key):
'''Return the value of the key.
Args:
key (str): dotted name of the key
Returns:
(any): value of the key, None if not found
'''
return self.get(key)
def get(self, key, default=None):
'''Return the value of the key.
Args:
key (str, list): dotted name of the key
default (any): value to return if key is not found (default: None)
Returns:
(any): value of the key
Examples:
>>> ns = NS(a={'b': {'c': 3}})
>>> ns['a:b:c'] == 3
True
>>> ns = NS()
>>> ns[u'a'] = {u'b': {u'c': 3}}
>>> ns[u'a:b:c'] == 3
True
'''
obj = self
parts = key
if isinstance(key, basestring):
parts = key.split(self.delimeter)
for part in parts:
if ismap(obj) and _in(obj, part):
obj = _get(obj, part)
else:
return default
return obj
def __setitem__(self, key, value):
'''Set the value of the key.
Args:
key (str, list): dotted name of the key
value (any): value of the key
'''
self.set(key, value)
def set(self, key, value):
'''Set the value of the key.
Args:
key (str, list): dotted name of the key
value (any): value of the key
Returns:
(NS): self for chaining
Examples:
>>> ns = NS(a=1, b=2, c={'d': 4})
>>> ns['a'] = 5
>>> ns['a'] == 5
True
>>> ns['c:d'] = 6
>>> ns['c:d'] == 6
True
>>> ns = NS()
>>> ns['a'] = {'b': {'c': 3}}
>>> ns['a:b:c'] == 3
True
>>> isinstance(ns['a:b'], NS)
True
'''
parts = key
if isinstance(key, basestring):
parts = key.split(self.delimeter)
obj = self
last = parts.pop()
for part in parts:
if not _in(obj, part) or not ismap(_get(obj, part)):
_set(obj, part, NS())
obj = _get(obj, part)
if ismap(value):
_set(obj, last, namespaceify(value, self.delimeter))
else:
_set(obj, last, value)
return self
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment