Advanced

Memory Efficiency and Meta Data

Node uses __slots__ for improved performance and memory efficiency.

As a side effect, it is not possible to assign new attributes to a node instance.

The meta slot can be used to attach arbitrary key/value pairs to a node.
Use get_meta(), set_meta(), update_meta(), and clear_meta(), to modify this node meta data:

node.set_meta("foo", 42)
assert node.get_meta("foo") == 42
node.set_meta("bar", "baz")

assert node._meta == {"foo": 42, "bar": "baz"}

Values are stored in a single dict node._meta, and is not used internally by nutree. However we should not access it directly, but use the provided methods instead.
The dict stores values _sparse_, i.e. only values != None are stored. This means that if you set a value to None, it will be removed from the dict.
If the dict is empty, node._meta will be set to None to save memory.

Iteration Callbacks

In the following sections we cover Search, Traversal, Mutation, etc. in detail.
Some methods described there, accept a predicate argument, for example copy(), filter(), find_all()

In all cases, the predicate callback is called with one single node argument and should return control value:

Note

The special values StopTraversal, SkipBranch, and SelectBranch can be returned as value or raised as exception.

find(), find_first()

The match callback can return these values:

  • False or None: No match: skip the node and continue traversal.

  • True: Stop iteration and return this node as result.

  • StopTraversal: Stop iteration and return None as result.

find_all()

The match callback can return these values:

  • False or None: No match: skip the node and continue traversal.

  • True: Add node to results and continue traversal.

  • SkipBranch: Skip node and its descendants, but continue iteration with next sibling.
    Return SkipBranch(and_self=False) to add the node to results, but skip descendants.

  • StopTraversal: Stop iteration and return current results.

visit()

The callback callback can return these values:

  • False: Stop iteration immediately.

  • StopTraversal: Stop iteration immediately. Return or raise StopTraversal(value) to specify a return value for the visit method.

  • SkipBranch: Skip descendants, but continue iteration with next sibling.

  • True, None, and all other values: No action: continue traversal.

copy(), filter(), filtered(), copy()

The predicate callback can return these values:

  • True: Keep the node and visit children.

  • False or None: Visit children and keep this node if at least one descendant is true.

  • SkipBranch: Skip node and its descendants, but continue iteration with next sibling.
    Return SkipBranch(and_self=False) to keep the node, but skip descendants.

  • SelectBranch: Unconditionally accept node and all descendants (do not call predicate()). In other words: copy the whole branch.

save() to_dict_list(), to_dot(), to_dotfile(), to_list_iter()

The mapper(node, data) callback can modify the dict argument data in-place (and return None) or return a new dict istance.

Locking

In multithreading scenarios, we can enforce critical sections like so:

with tree:
    snapshot = tree.to_dict_list()

Debugging

Call _self_check() to validate the internal data structures. This is slow and should not be done in production:

assert tree._self_check()

Performance Optimization

Most Node attributes are exposed as readonly properties. The real attribute is prefixed by an underscore.
In some situations, like close loops in critical algorithms it may be slightly faster to access attributes directly.

Warning

Use with care. Accessing or even modifying internal attributes may break the internal data structures.

When optimizing:

  1. Correctness before performance:

    Write simple, error free code first and cover it with unit tests, before starting to optimize.

  2. Do not guess or assume:

    Write benchmarks !

File System Helper

There is a simple helper that can be used to read a folder recursively:

from nutree import load_tree_from_fs

path = "/my/folder/path"
tree = load_tree_from_fs(path)
tree.print()
Tree</my/folder/path>
├── 'file_1.txt', 13 bytes
╰── [folder_1]
    ╰── 'file_1_1.txt', 15 bytes