Base class representing a node of a tree, with methods for traversing and altering the tree. This class stores no data, it has only parents and children attributes, and various methods. Stores child nodes in a dict, ensuring that equality checks between trees and order of child no
| 44 | |
| 45 | |
| 46 | class TreeNode: |
| 47 | """ |
| 48 | Base class representing a node of a tree, with methods for traversing and altering the tree. |
| 49 | |
| 50 | This class stores no data, it has only parents and children attributes, and various methods. |
| 51 | |
| 52 | Stores child nodes in a dict, ensuring that equality checks between trees |
| 53 | and order of child nodes is preserved (since python 3.7). |
| 54 | |
| 55 | Nodes themselves are intrinsically unnamed (do not possess a ._name attribute), but if the node has a parent you can |
| 56 | find the key it is stored under via the .name property. |
| 57 | |
| 58 | The .parent attribute is read-only: to replace the parent using public API you must set this node as the child of a |
| 59 | new parent using `new_parent.children[name] = child_node`, or to instead detach from the current parent use |
| 60 | `child_node.orphan()`. |
| 61 | |
| 62 | This class is intended to be subclassed by DataTree, which will overwrite some of the inherited behaviour, |
| 63 | in particular to make names an inherent attribute, and allow setting parents directly. The intention is to mirror |
| 64 | the class structure of xarray.Variable & xarray.DataArray, where Variable is unnamed but DataArray is (optionally) |
| 65 | named. |
| 66 | |
| 67 | Also allows access to any other node in the tree via unix-like paths, including upwards referencing via '../'. |
| 68 | |
| 69 | (This class is heavily inspired by the anytree library's NodeMixin class.) |
| 70 | |
| 71 | """ |
| 72 | |
| 73 | _parent: Self | None |
| 74 | _children: dict[str, Self] |
| 75 | |
| 76 | def __init__(self, children: Mapping[str, Self] | None = None): |
| 77 | """Create a parentless node.""" |
| 78 | self._parent = None |
| 79 | self._children = {} |
| 80 | |
| 81 | if children: |
| 82 | # shallow copy to avoid modifying arguments in-place (see GH issue #9196) |
| 83 | self.children = {name: child.copy() for name, child in children.items()} |
| 84 | |
| 85 | @property |
| 86 | def parent(self) -> Self | None: |
| 87 | """Parent of this node.""" |
| 88 | return self._parent |
| 89 | |
| 90 | @parent.setter |
| 91 | def parent(self, new_parent: Self) -> None: |
| 92 | raise AttributeError( |
| 93 | "Cannot set parent attribute directly, you must modify the children of the other node instead using dict-like syntax" |
| 94 | ) |
| 95 | |
| 96 | def _set_parent( |
| 97 | self, new_parent: Self | None, child_name: str | None = None |
| 98 | ) -> None: |
| 99 | # TODO is it possible to refactor in a way that removes this private method? |
| 100 | |
| 101 | if new_parent is not None and not isinstance(new_parent, TreeNode): |
| 102 | raise TypeError( |
| 103 | "Parent nodes must be of type DataTree or None, " |
no outgoing calls
searching dependent graphs…