Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RenderTree, LevelOrderIter etc access more child attributes than necessary when restricting maxlevel #214

Closed
wimglenn opened this issue Jan 18, 2023 · 2 comments
Assignees
Milestone

Comments

@wimglenn
Copy link
Contributor

wimglenn commented Jan 18, 2023

I am using anytree in johnnydep for rendering package dependencies. In my case, accessing the children attribute of a node instance creates the children dynamically, i.e. they are generated lazily on demand. There's a good reason for this- it entails a network request to get the package metadata, so we would like to avoid it if it won't actually be used (i.e. we never iterate to that depth).

That's when I found this issue- bounding the iterators such as LevelOrderIter and RenderTree by passing maxlevel still accesses more children attributes than necessary.

Here is an example reproducing the issue. If we use the example tree from the docs, where the full tree is:

Udo
├── Marc
│   └── Lian
└── Dan
    ├── Jet
    ├── Jan
    └── Joe

But pruning with maxlevel=2 it is just:

Udo
├── Marc
└── Dan

When we reveal "children" attribute access by overriding __getattribute__:

from anytree import Node as _Node, RenderTree, LevelOrderIter

maxlevel = 2

class Node(_Node):
    def __getattribute__(self, name):
        if name == "children":
            print("__getattribute__ children of node", self.name)
        return _Node.__getattribute__(self, name)

udo = Node("Udo")
marc = Node("Marc", parent=udo)
lian = Node("Lian", parent=marc)
dan = Node("Dan", parent=udo)
jet = Node("Jet", parent=dan)
jan = Node("Jan", parent=dan)
joe = Node("Joe", parent=dan)

for pre, fill, node in RenderTree(udo, maxlevel=maxlevel):
    print("%s%s" % (pre, node.name))

print()
for node in LevelOrderIter(udo, maxlevel=maxlevel):
    print(node)

I get this output:

Udo
__getattribute__ children of node Udo
├── Marc
__getattribute__ children of node Marc
└── Dan
__getattribute__ children of node Dan

Node('/Udo')
__getattribute__ children of node Udo
Node('/Udo/Marc')
__getattribute__ children of node Marc
Node('/Udo/Dan')
__getattribute__ children of node Dan

The children of Marc and Dan are accessed, even though they aren't used.

Would it be possible to refactor the iterators so that they access no more children than they strictly need to?

@c0fec0de c0fec0de self-assigned this Oct 11, 2023
@c0fec0de c0fec0de added this to the 2.10.0 milestone Oct 11, 2023
@c0fec0de
Copy link
Owner

Fixed in 2.10.0

@wimglenn
Copy link
Contributor Author

Thanks! Link for posterity 2e803f6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants