Powerful and Lightweight Python Tree Data Structure with various plugins
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive search functionality for finding nodes by custom filters or attribute values. Provides flexible node finding with depth limits, result count constraints, and performance-optimized cached variants.
Find the first node that matches specified criteria.
Find single node using custom filter function.
def find(node, filter_=None, stop=None, maxlevel=None):
"""
Find the first node matching filter.
Args:
node: Starting node for search
filter_: Function called with every node as argument, node is returned if True
stop: Stop iteration at node if stop function returns True for node
maxlevel: Maximum descending in the node hierarchy
Returns:
First matching node or None if no match found
"""Usage Example:
from anytree import Node, find
# Create tree structure
root = Node("Company")
engineering = Node("Engineering", parent=root)
marketing = Node("Marketing", parent=root)
backend = Node("Backend", parent=engineering, team_size=5)
frontend = Node("Frontend", parent=engineering, team_size=3)
content = Node("Content", parent=marketing, team_size=2)
# Find by name
eng_node = find(root, filter_=lambda n: n.name == "Engineering")
print(eng_node) # Node('/Company/Engineering')
# Find by attribute
large_team = find(root, filter_=lambda n: getattr(n, 'team_size', 0) >= 5)
print(large_team) # Node('/Company/Engineering/Backend')
# Find with custom logic
def has_substring(node):
return "end" in node.name.lower()
node_with_end = find(root, filter_=has_substring)
print(node_with_end) # Node('/Company/Engineering/Backend')Find single node by attribute value with exact matching.
def find_by_attr(node, value, name="name", maxlevel=None):
"""
Find single node by attribute value.
Args:
node: Starting node for search
value: Value to match
name: Attribute name to check (default "name")
maxlevel: Maximum search depth
Returns:
First node with matching attribute value or None
"""Usage Example:
from anytree import Node, find_by_attr
root = Node("Company")
Node("Engineering", parent=root, department_code="ENG")
Node("Marketing", parent=root, department_code="MKT")
Node("Backend", parent=root, department_code="ENG")
# Find by name (default attribute)
marketing = find_by_attr(root, "Marketing")
print(marketing) # Node('/Company/Marketing')
# Find by custom attribute
eng_dept = find_by_attr(root, "ENG", name="department_code")
print(eng_dept) # Node('/Company/Engineering') - first match
# With depth limit
shallow_search = find_by_attr(root, "Backend", maxlevel=2)
print(shallow_search) # None (Backend is at level 2, but search stops at level 2)Find all nodes that match specified criteria.
Find all nodes using custom filter function with optional count constraints.
def findall(node, filter_=None, stop=None, maxlevel=None, mincount=None, maxcount=None):
"""
Find all nodes matching filter.
Args:
node: Starting node for search
filter_: Function called with every node as argument, node is returned if True
stop: Stop iteration at node if stop function returns True for node
maxlevel: Maximum descending in the node hierarchy
mincount: Minimum number of nodes required
maxcount: Maximum number of nodes allowed
Returns:
Tuple of matching nodes
Raises:
CountError: If result count violates mincount/maxcount constraints
"""Usage Example:
from anytree import Node, findall, CountError
# Create tree structure
root = Node("Company")
engineering = Node("Engineering", parent=root, budget=100000)
marketing = Node("Marketing", parent=root, budget=50000)
backend = Node("Backend", parent=engineering, budget=60000)
frontend = Node("Frontend", parent=engineering, budget=40000)
content = Node("Content", parent=marketing, budget=30000)
# Find all nodes with budget > 50000
high_budget = findall(root, filter_=lambda n: getattr(n, 'budget', 0) > 50000)
print(len(high_budget)) # 3 nodes
for node in high_budget:
print(f"{node.name}: ${node.budget}")
# Find with count constraints
try:
# Require at least 2 matches
teams = findall(root,
filter_=lambda n: hasattr(n, 'budget') and n.budget < 100000,
mincount=2)
print(f"Found {len(teams)} teams with budget < $100k")
except CountError as e:
print(f"Count constraint failed: {e}")
# Find with max count limit
limited_results = findall(root,
filter_=lambda n: hasattr(n, 'budget'),
maxcount=3)
print(f"Limited to first {len(limited_results)} results")Find all nodes with matching attribute values.
def findall_by_attr(node, value, name="name", maxlevel=None, mincount=None, maxcount=None):
"""
Find all nodes by attribute value.
Args:
node: Starting node for search
value: Value to match
name: Attribute name to check (default "name")
maxlevel: Maximum search depth
mincount: Minimum number of nodes required
maxcount: Maximum number of nodes allowed
Returns:
Tuple of nodes with matching attribute value
Raises:
CountError: If result count violates mincount/maxcount constraints
"""Usage Example:
from anytree import Node, findall_by_attr
root = Node("Company")
Node("TeamA", parent=root, status="active")
Node("TeamB", parent=root, status="active")
Node("TeamC", parent=root, status="inactive")
Node("TeamD", parent=root, status="active")
# Find all active teams
active_teams = findall_by_attr(root, "active", name="status")
print(f"Active teams: {len(active_teams)}")
for team in active_teams:
print(f" {team.name}")
# Find all nodes with specific name pattern (multiple matches)
nodes_with_team = findall_by_attr(root, "Team", name="name") # Won't match - exact comparison
print(f"Exact 'Team' matches: {len(nodes_with_team)}") # 0
# For partial matching, use findall with filter
team_nodes = findall(root, filter_=lambda n: "Team" in n.name)
print(f"Nodes containing 'Team': {len(team_nodes)}") # 4Control search termination to avoid traversing certain subtrees:
from anytree import Node, findall
root = Node("Company")
public_dir = Node("public", parent=root)
private_dir = Node("private", parent=root, access="restricted")
Node("readme.txt", parent=public_dir, type="file")
Node("config.txt", parent=private_dir, type="file")
Node("secret.txt", parent=private_dir, type="file")
# Stop at restricted directories
public_files = findall(root,
filter_=lambda n: getattr(n, 'type', None) == 'file',
stop=lambda n: getattr(n, 'access', None) == 'restricted')
print(f"Public files: {len(public_files)}") # Only finds readme.txtSearch only within specified depth levels:
from anytree import Node, findall
# Create deep hierarchy
root = Node("L0")
l1 = Node("L1", parent=root)
l2 = Node("L2", parent=l1)
l3 = Node("L3", parent=l2)
Node("deep_target", parent=l3)
Node("shallow_target", parent=l1)
# Search only first 2 levels
shallow_search = findall(root,
filter_=lambda n: "target" in n.name,
maxlevel=2)
print(f"Shallow search found: {len(shallow_search)} targets") # 1
# Search all levels
deep_search = findall(root, filter_=lambda n: "target" in n.name)
print(f"Deep search found: {len(deep_search)} targets") # 2Combine multiple conditions in filter functions:
from anytree import Node, findall
root = Node("Company")
Node("Engineer_A", parent=root, role="engineer", experience=5, active=True)
Node("Manager_B", parent=root, role="manager", experience=8, active=True)
Node("Engineer_C", parent=root, role="engineer", experience=3, active=False)
Node("Engineer_D", parent=root, role="engineer", experience=7, active=True)
# Complex filter: active senior engineers (5+ years experience)
def senior_active_engineer(node):
return (getattr(node, 'role', '') == 'engineer' and
getattr(node, 'experience', 0) >= 5 and
getattr(node, 'active', False))
senior_engineers = findall(root, filter_=senior_active_engineer)
print(f"Senior active engineers: {len(senior_engineers)}")
for eng in senior_engineers:
print(f" {eng.name}: {eng.experience} years")High-performance cached versions of search functions for repeated queries on the same tree.
# Import cached search functions
from anytree import cachedsearch
# Same signatures as regular search functions but with caching
def cachedsearch.find(node, filter_=None, stop=None, maxlevel=None): ...
def cachedsearch.find_by_attr(node, value, name="name", maxlevel=None): ...
def cachedsearch.findall(node, filter_=None, stop=None, maxlevel=None, mincount=None, maxcount=None): ...
def cachedsearch.findall_by_attr(node, value, name="name", maxlevel=None, mincount=None, maxcount=None): ...Usage Example:
from anytree import Node, cachedsearch
# Create large tree
root = Node("root")
for i in range(1000):
Node(f"node_{i}", parent=root, category=f"cat_{i % 10}")
# First search - builds cache
start_time = time.time()
results1 = cachedsearch.findall_by_attr(root, "cat_5", name="category")
time1 = time.time() - start_time
# Second search - uses cache
start_time = time.time()
results2 = cachedsearch.findall_by_attr(root, "cat_5", name="category")
time2 = time.time() - start_time
print(f"First search: {time1:.4f}s")
print(f"Cached search: {time2:.4f}s (speedup: {time1/time2:.1f}x)")Raised when search results don't meet count constraints:
from anytree import Node, findall, CountError
root = Node("root")
Node("child1", parent=root)
Node("child2", parent=root)
try:
# Require at least 5 children
results = findall(root,
filter_=lambda n: n.parent == root,
mincount=5)
except CountError as e:
print(f"Not enough results: {e}")
try:
# Allow maximum 1 result
results = findall(root,
filter_=lambda n: n.parent == root,
maxcount=1)
except CountError as e:
print(f"Too many results: {e}")from anytree import Node, findall
root = Node("root")
# Add many nodes...
# Efficient: Check cheap conditions first
def efficient_filter(node):
# Quick attribute check first
if not hasattr(node, 'expensive_property'):
return False
# Expensive computation only if needed
return expensive_computation(node.expensive_property)
# Inefficient: Expensive check for every node
def inefficient_filter(node):
return expensive_computation(getattr(node, 'expensive_property', None))
# Use efficient filter
results = findall(root, filter_=efficient_filter)For repeated searches on the same tree structure, use cached search functions:
from anytree import cachedsearch
# Multiple searches on same tree - use cached versions
users_in_eng = cachedsearch.findall_by_attr(org_root, "Engineering", name="department")
managers = cachedsearch.findall_by_attr(org_root, "Manager", name="role")
senior_staff = cachedsearch.findall(org_root, filter_=lambda n: n.experience > 10)Use stop functions to avoid unnecessary traversal:
from anytree import Node, findall
# Stop searching once target is found in specific subtree
target_found = False
def stop_when_found(node):
global target_found
if node.name == "target":
target_found = True
return target_found and node.name == "expensive_subtree_root"
result = findall(root, filter_=lambda n: n.name == "target", stop=stop_when_found)Install with Tessl CLI
npx tessl i tessl/pypi-anytree