Part 1 — Getting Started¶
This page covers everything you need to go from zero to a working
NestedDictionary in a few minutes.
Installation¶
Install ndict-tools from PyPI using pip:
pip install ndict-tools
Or with uv:
uv add ndict-tools
The package requires Python 3.10 or later and has no runtime dependencies beyond the standard library.
Creating a nested dictionary¶
All three public classes share the same construction interface. The
simplest way is to pass a plain dict:
from ndict_tools import NestedDictionary
nd = NestedDictionary({"project": {"name": "ndict-tools", "version": "1.1.0"}})
You can also pass any iterable of (key, value) pairs or a zip:
nd = NestedDictionary(zip(["a", "b"], [{"x": 1}, 2]))
nd = NestedDictionary([("a", {"x": 1}), ("b", 2)])
To convert a pre-existing plain dictionary — including deeply nested ones
— use from_dict(). Because a plain
dict carries no information about how missing keys or printing
should behave, you must supply that configuration explicitly:
plain = {"europe": {"france": "Paris", "germany": "Berlin"}}
nd = NestedDictionary.from_dict(
plain,
default_setup={"indent": 2, "default_factory": NestedDictionary},
)
The default_setup dictionary accepts two keys:
indent— number of spaces used when printing (0disables indentation).default_factory— the class instantiated for missing keys; set toNestedDictionaryfor lenient behaviour orNonefor strict.
Reading, writing, and deleting¶
Standard single-key access works exactly like a plain dict:
nd = NestedDictionary({"a": {"b": 1}, "c": 2})
nd["a"] # NestedDictionary({'b': 1})
nd["c"] # 2
For multi-level access, pass a list of keys — a hierarchical key:
nd[["a", "b"]] # 1
nd[["a", "b"]] = 99 # write — intermediate levels created automatically
nd[["a", "b"]] # 99
del nd[["a", "b"]] # delete — empty parent 'a' is cleaned up automatically
"a" in nd # False
You can also chain standard attribute access:
nd["a"]["b"] # equivalent to nd[["a", "b"]]
Searching across all levels¶
Unlike a plain dict, a NestedDictionary
tracks every key at every depth. You can search without knowing the exact
path:
nd = NestedDictionary({
"europe": {"france": {"capital": "Paris"}},
"asia": {"japan": {"capital": "Tokyo"}},
})
nd.is_key("capital") # True — exists somewhere in the tree
nd.occurrences("capital") # 2 — appears at two different paths
nd.key_list("capital") # [('europe', 'france', 'capital'),
# ('asia', 'japan', 'capital')]
nd.items_list("capital") # ['Paris', 'Tokyo']
Choosing the right variant¶
All three classes share the same interface. The only difference is what happens when you access a key that does not exist.
Class |
Behaviour on unknown key |
|---|---|
Returns a new empty |
|
Raises |
|
Returns a new empty |
A quick rule of thumb:
Building a structure from scratch →
NestedDictionaryValidating or reading a known structure →
StrictNestedDictionaryNavigating an unknown structure without guards →
SmoothNestedDictionary