Package: attr-dicts
AttrDictThis package contains a generic dictionaryAttrDict, 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 whereconditionis 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 keykis some function ofvs = [d1[k], d2[k], ...].