Nested attributes utility functions for python. Allows getting/setting of object attributes and dict members interchangeably. Useful to populate nested dicts for storing outputs of a loop.
The functions should work with most dict types (i.e. Mapping / MutableMapping) and classes.
Tip: Attribute names can be found using regular expressions.
Install from PyPI:
pip install nattrsInstall from GitHub:
python -m pip install git+https://github.com/ludvigolsen/nattrs| Class/Function | Description |
|---|---|
nested_getattr |
Get object attributes/dict members recursively, given by dot-separated names. |
nested_setattr |
Set object attribute/dict member by recursive lookup, given by dot-separated names. |
nested_mutattr |
Apply function (mutate) to nested object attribute/dict member. |
nested_updattr |
Update dict / class.dict of nested attributes. |
nested_hasattr |
Check whether recursive object attributes/dict members exist. |
nested_delattr |
Delete nested object attributes/dict members. |
populate_product |
Create and populate nested dicts with specified layers and the same leaf value. |
Create class B with a dict c with the member d:
class B:
def __init__(self):
self.c = {
"d": 1
}Add to a dict a:
a = {"b": B()}Get the value of d:
nested_getattr(a, "b.c.d")
>> 1Get default value when not finding an attribute:
nested_getattr(a, "b.o.p", default="not found")
>> "not found"Set the value of d:
nested_setattr(a, "b.c.d", 2)Check new value of d:
nested_getattr(a, "b.c.d")
>> 2Mutate d with an anonymous function (lambda):
nested_mutattr(a, "b.c.d", lambda x: x * 5)Check new value of d:
nested_getattr(a, "b.c.d")
>> 10Note: If your function performs the assignment in-place, remember to enable the is_inplace_fn argument.
Update d with a dictionary:
nested_updattr(a, "b.c.d", {"d": 3})Check new value of d:
nested_getattr(a, "b.c.d")
>> 3Note: Also work on class.__dict__ dicts.
Check presence of the member 'd':
nested_hasattr(a, "b.c.d")
>> TrueFail to find member 'o':
nested_hasattr(a, "b.o.p")
>> FalseDelete the member 'd':
nested_delattr(a, "b.c.d")
nested_hasattr(a, "b.c.d")
>> FalseIgnore that it fails to find member 'o':
nested_delattr(a, "b.o.p", allow_missing=True)In this example, we wish to pre-populate nested dicts with empty lists to allow appending within a for loop. First, we go through the manual approach of doing this. Second, we show how easy it is to do with populate_product().
Say we have 3 variables that can each hold 2 values. We want to compute something for each combination of these values. Let's first define these variables and their options:
animal = ["cat", "dog"]
food = ["strawberry", "cucumber"]
temperature = ["cold", "warm"]Let's generate the product of these options:
import itertools
combinations = list(itertools.product(*[animal, food, temperature]))
combinations
>> [('cat', 'strawberry', 'cold'),
>> ('cat', 'strawberry', 'warm'),
>> ('cat', 'cucumber', 'cold'),
>> ('cat', 'cucumber', 'warm'),
>> ('dog', 'strawberry', 'cold'),
>> ('dog', 'strawberry', 'warm'),
>> ('dog', 'cucumber', 'cold'),
>> ('dog', 'cucumber', 'warm')]Now we can create a nested dict structure with a list in the leaf element:
# Initialize empty dict
nested_dict = {}
for leaf in combinations:
# Join each string with dot-separation:
attr = ".".join(list(leaf))
# Assign empty list to the leafs
# `make_missing` creates dicts for each
# missing attribute/dict member
nattrs.nested_setattr(
obj=nested_dict,
attr=attr,
value=[],
make_missing=True
)
nested_dict
>> {'cat': {'strawberry': {'cold': [], 'warm': []},
>> 'cucumber': {'cold': [], 'warm': []}},
>> 'dog': {'strawberry': {'cold': [], 'warm': []},
>> 'cucumber': {'cold': [], 'warm': []}}}This dict population is actually provided by populate_product(). Instead of an empty list, let's set the value to an "edibility" score that could be changed by a later function:
layers = [animal, food, temperature]
populate_product(
layers=layers,
val=False
)
>> {'cat': {'strawberry': {'cold': False, 'warm': False},
>> 'cucumber': {'cold': False, 'warm': False}},
>> 'dog': {'strawberry': {'cold': False, 'warm': False},
>> 'cucumber': {'cold': False, 'warm': False}}}