2. Usage
2.1. Principle
The core concept is straightforward: just as a dictionary can contain another dictionary as a value, a
NestedDictionary
naturally extends this idea. In a NestedDictionary
, each value that is itself a dictionary
must also be a NestedDictionary
. This recursive structure allows for seamless nesting of dictionaries within
dictionaries.
Unlike conventional dictionaries, nested keys in a NestedDictionary
are exposed as tuples. This representation
allows for easy access and manipulation of hierarchical data while maintaining compatibility with standard dictionary
operations.
$ a = NestedDictionary({'first': 1,
'second': {'1': "2:1", '2': "2:2", '3': "3:2"},
'third': 3,
'fourth': 4})
a's keys are:
[('first',), ('second', '1'), ('second', '2'), ('second', '3'), ('third',), ('fourth',)]
$ a['second']['1'] = "2:1"
2.2. Understanding Paths in Nested Dictionaries
In the context of nested dictionaries, a path is a sequence of keys that navigates through the hierarchical structure to access a specific value or sub-dictionary. Think of it as a trail of keys that leads you from the outermost dictionary to a particular point within the nested structure.
For example, consider the following nested dictionary:
data = {
'a': {
'b': {
'c': 1
},
'd': 2
},
'e': 3
}
In this dictionary:
The path
['a', 'b', 'c']
leads to the value1
.The path
['a', 'b']
leads to the dictionary{'c': 1}
.The path
['a', 'd']
leads to the value2
.The path
['e']
leads to the value3
.
By representing these paths as lists, we can easily describe and manipulate the hierarchical relationships within the dictionary. This concept is particularly useful when working with complex nested structures, as it provides a clear and concise way to reference specific elements.
2.3. Justification for Using Lists to Describe Paths
While standard dictionaries in Python use immutable types such as strings, numbers, or tuples as keys, representing hierarchical paths with lists offers several advantages in the context of nested dictionaries:
Sequential Representation: Lists naturally represent sequences, making them ideal for capturing the order of keys that lead to a specific value in a nested structure. This sequential nature aligns well with the concept of navigating through layers of dictionaries.
Flexibility: Lists are mutable, allowing for dynamic manipulation of paths. This flexibility is beneficial when working with complex or evolving data structures, where paths may need to be extended, modified, or truncated.
Readability: Using lists to represent paths enhances code readability. It provides a clear and intuitive way to understand the hierarchical relationships within the data, making the code easier to maintain and debug.
Compatibility with Recursive Operations: Lists are well-suited for recursive operations, which are common when traversing nested dictionaries. They can be easily passed to and modified within recursive functions, simplifying the implementation of algorithms that operate on hierarchical data.
Consistency with Existing Tools: Many existing tools and libraries that deal with hierarchical data structures, such as JSON or XML parsers, use lists or similar structures to represent paths. By adopting this convention, we maintain consistency with established practices.
2.4. Introducing DictPaths
To manage and access these paths efficiently, we provide the DictPaths
class. This class offers a view object that
provides a dictionary-like interface for accessing hierarchical keys as lists. Similar to dict_keys
, but tailored
for hierarchical paths in a _StackedDict
, DictPaths
allows you to:
Iterate over all hierarchical paths in the
_StackedDict
as lists.Check if a specific hierarchical path exists within the
_StackedDict
.Retrieve the number of hierarchical paths present in the
_StackedDict
.
By using DictPaths
, you can easily navigate and manipulate complex nested dictionary structures, making your code
more readable and maintainable.
2.5. Behavior
Nested dictionaries inherit from defaultdict. The default_factory attribute characterizes the behavior of this class:
If the nested dictionary is to behave strictly like a dictionary, then the default_factory attribute is set to None.
If you request the value of a key that doesn’t exist, you’ll get a KeyError. The configuration parameter is
strict=True
>>> from ndict_tools import NestedDictionary
>>> nd = NestedDictionary({'first': 1,
'second': {'1': "2:1", '2': "2:2", '3': "3:2"},
'third': 3,
'fourth': 4},
strict=True)
nd.default_factory
>>> nd['fifth']
Traceback (most recent call last):
File "/snap/pycharm-professional/401/plugins/python/helpers/pydev/pydevconsole.py", line 364, in runcode
coro = func()
File "<input>", line 1, in <module>
KeyError: 'fifth'
If the nested dictionary is to have flexible behavior, then the default_factory attribute is set to NestedDictionary.
If you request a key that doesn’t exist, a NestedDictionary instance will be created accordingly and returned. The
configuration parameter is strict=False
or no parameter
>>> from ndict_tools import NestedDictionary
>>> nd = NestedDictionary({'first': 1,
'second': {'1': "2:1", '2': "2:2", '3': "3:2"},
'third': 3,
'fourth': 4},
strict=False)
>>> nd.default_factory
<class 'ndict_tools.core.NestedDictionary'>
>>> nd['fifth']
NestedDictionary(<class 'ndict_tools.core.NestedDictionary'>, {})
And with no parameter
>>> from ndict_tools import NestedDictionary
>>> nd = NestedDictionary({'first': 1,
'second': {'1': "2:1", '2': "2:2", '3': "3:2"},
'third': 3,
'fourth': 4})
>>> nd.default_factory
<class 'ndict_tools.core.NestedDictionary'>
>>> nd['fifth']
NestedDictionary(<class 'ndict_tools.core.NestedDictionary'>, {})
2.6. Examples
$ a = NestedDictionary({'first': 1,
'second': {'1': "2:1", '2': "2:2", '3': "3:2"},
'third': 3,
'fourth': 4})
$ b = NestedDictionary(zip(['first', 'second', 'third', 'fourth'],
[1, {'1': "2:1", '2': "2:2", '3': "3:2"}, 3, 4]))
$ c = NestedDictionary([('first', 1),
('second', {'1': "2:1", '2': "2:2", '3': "3:2"}),
('third', 3),
('fourth', 4)])
$ d = NestedDictionary([('third', 3),
('first', 1),
('second', {'1': "2:1", '2': "2:2", '3': "3:2"}),
('fourth', 4)])
$ e = NestedDictionary([('first', 1), ('fourth', 4)],
third = 3,
second = {'1': "2:1", '2': "2:2", '3': "3:2"})
a == b == c == d == e
2.7. Class attributes and methods
- class ndict_tools.NestedDictionary(*args, **kwargs)
Nested dictionary class.
This class is designed as a stacked dictionary. It represents a nest of dictionaries, that is to say that each key is a value or a nested dictionary. And so on…
This function initializes a nested dictionary.
- Parameters:
args (Iterable) – the first one of the list must be a dictionary to instantiate an object.
kwargs (dict) –
enrichments settings and
indent : indentation of the printable nested dictionary (used by json.dumps() function)
strict : strict mode (False by default) define default answer to unknown key
Example
NestedDictionary({'first': 1,'second': {'1': "2:1", '2': "2:2", '3': "3:2"}, 'third': 3, 'fourth': 4})
NestedDictionary(zip(['first','second', 'third', 'fourth'], [1, {'1': "2:1", '2': "2:2", '3': "3:2"}, 3, 4]))
NestedDictionary([('first', 1), ('second', {'1': "2:1", '2': "2:2", '3': "3:2"}), ('third', 3), ('fourth', 4)])
- indent: int = 0
indent is used to print the dictionary with json indentation
- default_factory
Factory for default value called by __missing__().
- update()
Updates a stacked dictionary with key/value pairs from a dictionary or keyword arguments.
- Parameters:
dictionary (dict) – A dictionary with key/value pairs to update.
kwargs (dict) – Additional key/value pairs to update.
- Returns:
None
- occurrences()
Returns the Number of occurrences of a key in a stacked dictionary including 0 if the key is not a keys in a stacked dictionary.
- Parameters:
key (Any) – A possible key in a stacked dictionary.
- Returns:
Number of occurrences or 0
- Return type:
int
- is_key()
Checks if a key exists at any level in the _StackedDict hierarchy using unpack_items(). This works for both flat keys (e.g., 1) and hierarchical keys (e.g., [1, 2, 3]).
- Parameters:
key – A key to check. Can be a single key or a part of a hierarchical path.
- Returns:
True if the key exists at any level, False otherwise.
- key_list()
returns the list of unpacked keys containing the key from the stacked dictionary. If the key is not in the dictionary, it raises StackedKeyError (not a key).
- Parameters:
key (Any) – a possible key in a stacked dictionary.
- Returns:
A list of unpacked keys containing the key from the stacked dictionary.
- Return type:
list
- Raises:
StackedKeyError – if a key is not in a stacked dictionary.
- items_list()
returns the list of unpacked items associated to the key from the stacked dictionary. If the key is not in the dictionary, it raises StackedKeyError (not a key).
- Parameters:
key (Any) – a possible key in a stacked dictionary.
- Returns:
A list of unpacked items associated the key from the stacked dictionary.
- Return type:
list
- Raises:
StackedKeyError – if a key is not in a stacked dictionary.
- to_dict()
This method converts a nested dictionary to a classical dictionary
- Returns:
a dictionary
- Return type:
dict