This package contains a generic dictionary AttrDict
, which implements nested dictionaries that are easy to access, filter, combine, and write to.
Installation: pip install attr-dicts
:
PyPI: https://pypi.org/project/attr-dicts/
Creation: Let's say we want to store the following nested structure of arrays:
- food
- carrot: [1,2,3]
- apple: [4,5,6]
- broccoli: [7,8,9,10]
- utensils
- fork
- three_prong: [11,12]
- four_prong: [13,14,15]
- spoon: [16,17,18]
AttrDicts use '/' to separate keys, and this is built in to read/write operations. Here's two examples of how to instantiate the above structure.
d = AttrDict()
d['food/carrot'] = [1,2,3]
d.food.apple = [4,5,6]
d['food/broccoli'] = [7,8,9,10]
d['utensils/fork/three_prong'] = [11,12]
d['utensils/fork/four_prong'] = [13,14,15]
d['utensils/spoon'] = [16,17,18]
Note that both indexing and dot access work, since AttrDicts inherit from the DotMap class. Here's a slightly less effort way:
d = AttrDict(
food=AttrDict(carrot=[1,2,3], apple=[4,5,6], broccoli=[7,8,9,10]),
utensils=AttrDict(
fork=AttrDict(three_prong=[11,12], four_prong=[13,14,15]),
spoon=[16,17,18]
)
)
Access: There are several ways to access an AttrDict.
d['utensils/fork/three_prong']
: standard dictionary access, but using '/' to implicitly sub-index (KeyError if missing)d.utensils.fork.three_prong
: dotmap access (AttributeError if missing)d.utensils['fork/three_prong']
: mixed indexing + dotmap (either AttributeError or KeyError if missing)d >> 'utensils/fork/three_prong
: (DEPRECATED) required key access, will error if not present.d << 'utensils/fork/three_prong
: optional key access, will return None if not presentd > ['utensils/fork/three_prong,'utensils/spoon']
: required key filtering, returns sub-dict. errors if a key in the arg list is not present.d < ['utensils/fork/three_prong,'utensils/spoon']
: optional key access, returns sub-dict, ignores keys that aren't present.
Node/Leaf operations: Leaf nodes are any access pattern that returns something that isn't an AttrDict. In the above example, 'food' is a node key, while 'food/carrot' is a leaf key. We can operate on all leaf nodes at once, here are some example methods:
d.leaf_keys()
: Generator that yields leaf keys under a depth first traverse.d.list_leaf_keys()
: Outputs a list instead of generator.d.leaf_values()
: Generator that yields leaf values under a depth first traverse.applied_d = d.leaf_apply(lambda v: <new_v>)
: Apply a function(value) on all leaf values, and create a new AttrDict.filtered_d = d.leaf_filter(lambda k,v: <condition>)
: Only keep leaf keys wherecondition
is true in new AttrDict.
Similarly, there are functions that operate on both nodes and leaves.
Combining: Combining AttrDicts can be done in several ways:
new_d = d1 & d2
: Standard join, returns a new AttrDict, which will favor keys from d2 if there are duplicates.d1.combine(d2)
: Mutates d1 to join the arrays.new_d = AttrDict.leaf_combine_and_apply([d1, d2, ...], lambda vs: <return one value>)
: Given a list of AttrDicts with the same keys, will create one AttrDict where the value for a given keyk
is some function ofvs = [d1[k], d2[k], ...]
.