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 nattrs
Install 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")
>> 1
Get 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")
>> 2
Mutate 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")
>> 10
Note: 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")
>> 3
Note: Also work on class.__dict__
dicts.
Check presence of the member 'd':
nested_hasattr(a, "b.c.d")
>> True
Fail to find member 'o':
nested_hasattr(a, "b.o.p")
>> False
Delete the member 'd':
nested_delattr(a, "b.c.d")
nested_hasattr(a, "b.c.d")
>> False
Ignore 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}}}