Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions USAGE.TXT
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,38 @@
nd4["human"]["chr1"]+=3
nd4["human"]["chr3"]+=4

3) Dictionaries with lists or sets (or arbitrary objects) can have custom combine_policies:


> nd1 = nested_dict({'a':1,'f':[1,3]})
> nd2 = nested_dict({'a':1,'f':[1,2]})

# use built-in combine_policy to merge lists
> nd1.update(nd2, combine_policies=['uniquely_extend_list'])
{'a':1,'f':[1,3,2]}

Here we are using the built-in combine_policy_options.
If your dict contains structures you may specify which ones update() will use with the combine_policies kwarg (list).
You may also supply your own combine_policy_options kwarg (list)

default_combine_policy_options=[
{'name': 'uniquely_extend_list', 'signature': (list,list),
'combiner': lambda x,y: x + list(set(y) - set(x)) },

{'name': 'list_of_union', 'signature': (list,list),
'combiner': lambda x,y: list(set(y) + set(x)) },

{'name': 'symmetric_difference', 'signature': (set,set),
'combiner': lambda x,y: x.symmetric_difference(y)},
]

Here is an example of creating your own useful combiner

> combine_policy_options=[
{'name': 'rabbits', 'signature': (list,list),
'combiner': lambda x,y: 'look rabbits' }]
> nd1 = nested_dict.nested_dict({'a':1,'f':[1,3,3]})
> nd2 = nested_dict.nested_dict({'a':1,'f':[1,2]})
> nd1.update(nd2,
combine_policies=['rabbits'], combine_plicy_options=combine_policy_options)
{'a':1,'f':'look rabbits'})
35 changes: 29 additions & 6 deletions nested_dict/implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,26 +149,47 @@ def nested_dict_from_dict(orig_dict, nd):
nd[key] = value
return nd

default_combine_policy_options=[
{'name': 'uniquely_extend_list', 'signature': (list,list),
'combiner': lambda x,y: x + [yy for yy in y if yy not in x] },
{'name': 'sorted_list_of_union', 'signature': (list,list),
'combiner': lambda x,y: sorted(list(set(y).union(set(x)))) },
{'name': 'symmetric_difference', 'signature': (set,set),
'combiner': lambda x,y: x.symmetric_difference(y)},
]


def get_combine_policy(val1, val2, combine_policies, combine_policy_options):
for option in combine_policy_options:
if ((type(val1),type(val2)) == option['signature']) and (option['name'] in combine_policies):
return option['combiner']
else:
return None

def _recursive_update(nd, other):
def _recursive_update(nd, other, combine_policies=[], combine_policy_options=[]):
for key, value in iteritems(other):
#print ("key=", key)
combine_policy = get_combine_policy(nd[key], other[key], combine_policies, combine_policy_options)
#print("combine_policy for {} {} is {}".format(key, value, combine_policy))
if isinstance(value, (dict,)):

# recursive update if my item is nested_dict
if isinstance(nd[key], (_recursive_dict,)):
#print ("recursive update", key, type(nd[key]))
_recursive_update(nd[key], other[key])
_recursive_update(nd[key], other[key], combine_policies)

# update if my item is dict
elif isinstance(nd[key], (dict,)):
#print ("update", key, type(nd[key]))
nd[key].update(other[key])

nd[key].update(other[key], combine_policies)
# overwrite
else:
#print ("self not nested dict or dict: overwrite", key)
nd[key] = value
# combine by specified matching combine_policy
elif combine_policy:
#print(nd[key], combine_policy(nd[key], value))
nd[key] = combine_policy(nd[key], value)
# other not dict: overwrite
else:
#print ("other not dict: overwrite", key)
Expand All @@ -188,9 +209,10 @@ class nested_dict(_recursive_dict):
Uses defaultdict to automatically add levels of nested dicts and other types.
"""

def update(self, other):
def update(self, other, combine_policies=[], combine_policy_options=[]):
"""Update recursively."""
_recursive_update(self, other)
combine_policy_options = combine_policy_options + default_combine_policy_options
_recursive_update(self, other, combine_policies, combine_policy_options)

def __init__(self, *param, **named_param):
"""
Expand Down Expand Up @@ -229,3 +251,4 @@ def __init__(self, *param, **named_param):
"2) an existing dict to be converted into a nested dict "
"(factory = %s. len(param) = %d, param = %s"
% (self.factory, len(param), param))

32 changes: 32 additions & 0 deletions tests/test_nested_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,35 @@ def test_update(self):
# d1[2][3][4][5] = 6 but d1[2][3][5] should still be a default dict of list
d1[2][3][5].append(4)
self.assertEqual(d1.to_dict(), {1: {2: {3: [4], 4: [4]}}, 2: {3: {4: {5: 6}, 5: [4]}}})

def test_update_with_combine_policies(self):
"""Test update with combine_policies"""
import nested_dict

#
# uniquely_extend_list
#
nd1 = nested_dict.nested_dict({'a':1,'f':[1,3,3]})
nd2 = nested_dict.nested_dict({'a':1,'f':[1,2]})
nd1.update(nd2, combine_policies=['uniquely_extend_list'])
self.assertEqual(nd1.to_dict(), {'a':1,'f':[1,3,3,2]})

#
# list_of_union
#
nd1 = nested_dict.nested_dict({'a':1,'f':[1,3,3]})
nd2 = nested_dict.nested_dict({'a':1,'f':[1,2]})
nd1.update(nd2, combine_policies=['sorted_list_of_union'])
self.assertEqual(nd1.to_dict(), {'a':1,'f':[1,2,3]})

#
# custom_combine_policy
#
combine_policy_options=[
{'name': 'rabbits', 'signature': (list,list),
'combiner': lambda x,y: 'look rabbits' }
]
nd1 = nested_dict.nested_dict({'a':1,'f':[1,3,3]})
nd2 = nested_dict.nested_dict({'a':1,'f':[1,2]})
nd1.update(nd2, combine_policies=['rabbits'], combine_policy_options=combine_policy_options)
self.assertEqual(nd1.to_dict(), {'a':1,'f':'look rabbits'})