0
# Search and Filtering
1
2
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.
3
4
## Capabilities
5
6
### Single Node Search
7
8
Find the first node that matches specified criteria.
9
10
#### find - Custom Filter Search
11
12
Find single node using custom filter function.
13
14
```python { .api }
15
def find(node, filter_=None, stop=None, maxlevel=None):
16
"""
17
Find the first node matching filter.
18
19
Args:
20
node: Starting node for search
21
filter_: Function called with every node as argument, node is returned if True
22
stop: Stop iteration at node if stop function returns True for node
23
maxlevel: Maximum descending in the node hierarchy
24
25
Returns:
26
First matching node or None if no match found
27
"""
28
```
29
30
**Usage Example:**
31
32
```python
33
from anytree import Node, find
34
35
# Create tree structure
36
root = Node("Company")
37
engineering = Node("Engineering", parent=root)
38
marketing = Node("Marketing", parent=root)
39
backend = Node("Backend", parent=engineering, team_size=5)
40
frontend = Node("Frontend", parent=engineering, team_size=3)
41
content = Node("Content", parent=marketing, team_size=2)
42
43
# Find by name
44
eng_node = find(root, filter_=lambda n: n.name == "Engineering")
45
print(eng_node) # Node('/Company/Engineering')
46
47
# Find by attribute
48
large_team = find(root, filter_=lambda n: getattr(n, 'team_size', 0) >= 5)
49
print(large_team) # Node('/Company/Engineering/Backend')
50
51
# Find with custom logic
52
def has_substring(node):
53
return "end" in node.name.lower()
54
55
node_with_end = find(root, filter_=has_substring)
56
print(node_with_end) # Node('/Company/Engineering/Backend')
57
```
58
59
#### find_by_attr - Attribute Value Search
60
61
Find single node by attribute value with exact matching.
62
63
```python { .api }
64
def find_by_attr(node, value, name="name", maxlevel=None):
65
"""
66
Find single node by attribute value.
67
68
Args:
69
node: Starting node for search
70
value: Value to match
71
name: Attribute name to check (default "name")
72
maxlevel: Maximum search depth
73
74
Returns:
75
First node with matching attribute value or None
76
"""
77
```
78
79
**Usage Example:**
80
81
```python
82
from anytree import Node, find_by_attr
83
84
root = Node("Company")
85
Node("Engineering", parent=root, department_code="ENG")
86
Node("Marketing", parent=root, department_code="MKT")
87
Node("Backend", parent=root, department_code="ENG")
88
89
# Find by name (default attribute)
90
marketing = find_by_attr(root, "Marketing")
91
print(marketing) # Node('/Company/Marketing')
92
93
# Find by custom attribute
94
eng_dept = find_by_attr(root, "ENG", name="department_code")
95
print(eng_dept) # Node('/Company/Engineering') - first match
96
97
# With depth limit
98
shallow_search = find_by_attr(root, "Backend", maxlevel=2)
99
print(shallow_search) # None (Backend is at level 2, but search stops at level 2)
100
```
101
102
### Multiple Node Search
103
104
Find all nodes that match specified criteria.
105
106
#### findall - Custom Filter Search
107
108
Find all nodes using custom filter function with optional count constraints.
109
110
```python { .api }
111
def findall(node, filter_=None, stop=None, maxlevel=None, mincount=None, maxcount=None):
112
"""
113
Find all nodes matching filter.
114
115
Args:
116
node: Starting node for search
117
filter_: Function called with every node as argument, node is returned if True
118
stop: Stop iteration at node if stop function returns True for node
119
maxlevel: Maximum descending in the node hierarchy
120
mincount: Minimum number of nodes required
121
maxcount: Maximum number of nodes allowed
122
123
Returns:
124
Tuple of matching nodes
125
126
Raises:
127
CountError: If result count violates mincount/maxcount constraints
128
"""
129
```
130
131
**Usage Example:**
132
133
```python
134
from anytree import Node, findall, CountError
135
136
# Create tree structure
137
root = Node("Company")
138
engineering = Node("Engineering", parent=root, budget=100000)
139
marketing = Node("Marketing", parent=root, budget=50000)
140
backend = Node("Backend", parent=engineering, budget=60000)
141
frontend = Node("Frontend", parent=engineering, budget=40000)
142
content = Node("Content", parent=marketing, budget=30000)
143
144
# Find all nodes with budget > 50000
145
high_budget = findall(root, filter_=lambda n: getattr(n, 'budget', 0) > 50000)
146
print(len(high_budget)) # 3 nodes
147
for node in high_budget:
148
print(f"{node.name}: ${node.budget}")
149
150
# Find with count constraints
151
try:
152
# Require at least 2 matches
153
teams = findall(root,
154
filter_=lambda n: hasattr(n, 'budget') and n.budget < 100000,
155
mincount=2)
156
print(f"Found {len(teams)} teams with budget < $100k")
157
except CountError as e:
158
print(f"Count constraint failed: {e}")
159
160
# Find with max count limit
161
limited_results = findall(root,
162
filter_=lambda n: hasattr(n, 'budget'),
163
maxcount=3)
164
print(f"Limited to first {len(limited_results)} results")
165
```
166
167
#### findall_by_attr - Attribute Value Search
168
169
Find all nodes with matching attribute values.
170
171
```python { .api }
172
def findall_by_attr(node, value, name="name", maxlevel=None, mincount=None, maxcount=None):
173
"""
174
Find all nodes by attribute value.
175
176
Args:
177
node: Starting node for search
178
value: Value to match
179
name: Attribute name to check (default "name")
180
maxlevel: Maximum search depth
181
mincount: Minimum number of nodes required
182
maxcount: Maximum number of nodes allowed
183
184
Returns:
185
Tuple of nodes with matching attribute value
186
187
Raises:
188
CountError: If result count violates mincount/maxcount constraints
189
"""
190
```
191
192
**Usage Example:**
193
194
```python
195
from anytree import Node, findall_by_attr
196
197
root = Node("Company")
198
Node("TeamA", parent=root, status="active")
199
Node("TeamB", parent=root, status="active")
200
Node("TeamC", parent=root, status="inactive")
201
Node("TeamD", parent=root, status="active")
202
203
# Find all active teams
204
active_teams = findall_by_attr(root, "active", name="status")
205
print(f"Active teams: {len(active_teams)}")
206
for team in active_teams:
207
print(f" {team.name}")
208
209
# Find all nodes with specific name pattern (multiple matches)
210
nodes_with_team = findall_by_attr(root, "Team", name="name") # Won't match - exact comparison
211
print(f"Exact 'Team' matches: {len(nodes_with_team)}") # 0
212
213
# For partial matching, use findall with filter
214
team_nodes = findall(root, filter_=lambda n: "Team" in n.name)
215
print(f"Nodes containing 'Team': {len(team_nodes)}") # 4
216
```
217
218
## Advanced Search Patterns
219
220
### Stop Functions
221
222
Control search termination to avoid traversing certain subtrees:
223
224
```python
225
from anytree import Node, findall
226
227
root = Node("Company")
228
public_dir = Node("public", parent=root)
229
private_dir = Node("private", parent=root, access="restricted")
230
Node("readme.txt", parent=public_dir, type="file")
231
Node("config.txt", parent=private_dir, type="file")
232
Node("secret.txt", parent=private_dir, type="file")
233
234
# Stop at restricted directories
235
public_files = findall(root,
236
filter_=lambda n: getattr(n, 'type', None) == 'file',
237
stop=lambda n: getattr(n, 'access', None) == 'restricted')
238
239
print(f"Public files: {len(public_files)}") # Only finds readme.txt
240
```
241
242
### Depth-Limited Search
243
244
Search only within specified depth levels:
245
246
```python
247
from anytree import Node, findall
248
249
# Create deep hierarchy
250
root = Node("L0")
251
l1 = Node("L1", parent=root)
252
l2 = Node("L2", parent=l1)
253
l3 = Node("L3", parent=l2)
254
Node("deep_target", parent=l3)
255
Node("shallow_target", parent=l1)
256
257
# Search only first 2 levels
258
shallow_search = findall(root,
259
filter_=lambda n: "target" in n.name,
260
maxlevel=2)
261
print(f"Shallow search found: {len(shallow_search)} targets") # 1
262
263
# Search all levels
264
deep_search = findall(root, filter_=lambda n: "target" in n.name)
265
print(f"Deep search found: {len(deep_search)} targets") # 2
266
```
267
268
### Complex Filter Logic
269
270
Combine multiple conditions in filter functions:
271
272
```python
273
from anytree import Node, findall
274
275
root = Node("Company")
276
Node("Engineer_A", parent=root, role="engineer", experience=5, active=True)
277
Node("Manager_B", parent=root, role="manager", experience=8, active=True)
278
Node("Engineer_C", parent=root, role="engineer", experience=3, active=False)
279
Node("Engineer_D", parent=root, role="engineer", experience=7, active=True)
280
281
# Complex filter: active senior engineers (5+ years experience)
282
def senior_active_engineer(node):
283
return (getattr(node, 'role', '') == 'engineer' and
284
getattr(node, 'experience', 0) >= 5 and
285
getattr(node, 'active', False))
286
287
senior_engineers = findall(root, filter_=senior_active_engineer)
288
print(f"Senior active engineers: {len(senior_engineers)}")
289
290
for eng in senior_engineers:
291
print(f" {eng.name}: {eng.experience} years")
292
```
293
294
## Cached Search Functions
295
296
High-performance cached versions of search functions for repeated queries on the same tree.
297
298
### Module: cachedsearch
299
300
```python { .api }
301
# Import cached search functions
302
from anytree import cachedsearch
303
304
# Same signatures as regular search functions but with caching
305
def cachedsearch.find(node, filter_=None, stop=None, maxlevel=None): ...
306
def cachedsearch.find_by_attr(node, value, name="name", maxlevel=None): ...
307
def cachedsearch.findall(node, filter_=None, stop=None, maxlevel=None, mincount=None, maxcount=None): ...
308
def cachedsearch.findall_by_attr(node, value, name="name", maxlevel=None, mincount=None, maxcount=None): ...
309
```
310
311
**Usage Example:**
312
313
```python
314
from anytree import Node, cachedsearch
315
316
# Create large tree
317
root = Node("root")
318
for i in range(1000):
319
Node(f"node_{i}", parent=root, category=f"cat_{i % 10}")
320
321
# First search - builds cache
322
start_time = time.time()
323
results1 = cachedsearch.findall_by_attr(root, "cat_5", name="category")
324
time1 = time.time() - start_time
325
326
# Second search - uses cache
327
start_time = time.time()
328
results2 = cachedsearch.findall_by_attr(root, "cat_5", name="category")
329
time2 = time.time() - start_time
330
331
print(f"First search: {time1:.4f}s")
332
print(f"Cached search: {time2:.4f}s (speedup: {time1/time2:.1f}x)")
333
```
334
335
## Exception Handling
336
337
### CountError Exception
338
339
Raised when search results don't meet count constraints:
340
341
```python
342
from anytree import Node, findall, CountError
343
344
root = Node("root")
345
Node("child1", parent=root)
346
Node("child2", parent=root)
347
348
try:
349
# Require at least 5 children
350
results = findall(root,
351
filter_=lambda n: n.parent == root,
352
mincount=5)
353
except CountError as e:
354
print(f"Not enough results: {e}")
355
356
try:
357
# Allow maximum 1 result
358
results = findall(root,
359
filter_=lambda n: n.parent == root,
360
maxcount=1)
361
except CountError as e:
362
print(f"Too many results: {e}")
363
```
364
365
## Search Performance Tips
366
367
### Filter Function Optimization
368
369
```python
370
from anytree import Node, findall
371
372
root = Node("root")
373
# Add many nodes...
374
375
# Efficient: Check cheap conditions first
376
def efficient_filter(node):
377
# Quick attribute check first
378
if not hasattr(node, 'expensive_property'):
379
return False
380
# Expensive computation only if needed
381
return expensive_computation(node.expensive_property)
382
383
# Inefficient: Expensive check for every node
384
def inefficient_filter(node):
385
return expensive_computation(getattr(node, 'expensive_property', None))
386
387
# Use efficient filter
388
results = findall(root, filter_=efficient_filter)
389
```
390
391
### Using Cached Search
392
393
For repeated searches on the same tree structure, use cached search functions:
394
395
```python
396
from anytree import cachedsearch
397
398
# Multiple searches on same tree - use cached versions
399
users_in_eng = cachedsearch.findall_by_attr(org_root, "Engineering", name="department")
400
managers = cachedsearch.findall_by_attr(org_root, "Manager", name="role")
401
senior_staff = cachedsearch.findall(org_root, filter_=lambda n: n.experience > 10)
402
```
403
404
### Early Termination
405
406
Use stop functions to avoid unnecessary traversal:
407
408
```python
409
from anytree import Node, findall
410
411
# Stop searching once target is found in specific subtree
412
target_found = False
413
414
def stop_when_found(node):
415
global target_found
416
if node.name == "target":
417
target_found = True
418
return target_found and node.name == "expensive_subtree_root"
419
420
result = findall(root, filter_=lambda n: n.name == "target", stop=stop_when_found)
421
```