Iterator
coola.iterator ¶
Utilities to traverse and filter nested data structures.
dfs_iterate and bfs_iterate are lazy generators that yield the
leaves of a nested structure, once per occurrence and never a container,
in depth-first (pre-order) or breadth-first (level order) order. The
child finder of an object is chosen from its type along the Method
Resolution Order, and one child finder serves both orders. Keys are
never yielded, the input is never modified, iterators and generators are
leaves (never consumed), and a container that contains itself raises a
ValueError. Both orders yield the same leaves; only the order
differs, and neither is limited by the depth of the data. dfs_items
yields the same leaves with their path.
coola.iterator.BaseChildFinder ¶
Bases: ABC, Generic[T]
Abstract base class for the child finders.
A child finder describes the shape of the values of a type. It is
registered in a ChildFinderRegistry and is used by
dfs_iterate and bfs_iterate. The generic type parameter
T indicates the type of data this child finder handles.
Notes
- Subclasses must implement the
find_childrenmethod. find_childrenis pure and cheap: it does not call the registry, does not recurse, does not modify the data and does not consume the children (it returns them).- The result is consumed once, lazily, in order. It can be
the container itself, a view (
data.values()) or a generator. - A leaf returns
None: the traversal yields the value itself. A container with no child returns an empty iterable: it is not a leaf and yields nothing. - The children are the values to visit: a child finder never returns the keys of a mapping.
Example
>>> from coola.iterator import BaseChildFinder, dfs_iterate, register_child_finders
>>> class Node:
... def __init__(self, left, right):
... self.left, self.right = left, right
...
>>> class NodeChildFinder(BaseChildFinder[Node]):
... def find_children(self, data):
... return [data.left, data.right]
...
>>> register_child_finders({Node: NodeChildFinder()})
>>> list(dfs_iterate(Node(1, Node(2, 3))))
[1, 2, 3]
coola.iterator.BaseChildFinder.find_children
abstractmethod
¶
find_children(data: T) -> Iterable[Any] | None
Find the children of the data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
T
|
The data whose children should be found. |
required |
Returns:
| Type | Description |
|---|---|
Iterable[Any] | None
|
|
Example
>>> from coola.iterator import DefaultChildFinder, IterableChildFinder
>>> print(DefaultChildFinder().find_children(42))
None
>>> IterableChildFinder().find_children([1, 2])
[1, 2]
coola.iterator.BaseChildFinder.find_items ¶
find_items(data: T) -> Iterable[tuple[Any, Any]] | None
Find the labelled children of the data.
The label of a child is what dfs_items puts in the path of
the leaves below it. The default implementation numbers the
children returned by find_children (enumerate), so a
child finder that only implements find_children works with
dfs_items and gets integer labels. Override it to give the
raw key, index or name of each child.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
T
|
The data whose children should be found. |
required |
Returns:
| Type | Description |
|---|---|
Iterable[tuple[Any, Any]] | None
|
|
Example
>>> from coola.iterator import DefaultChildFinder, MappingChildFinder
>>> print(DefaultChildFinder().find_items(42))
None
>>> list(MappingChildFinder().find_items({"a": 1, "b": 2}))
[('a', 1), ('b', 2)]
coola.iterator.ChildFinder ¶
Bases: Protocol[T_contra]
Protocol of the child finders.
A child finder only describes the shape of a type: it returns the
children of a value. The traversal (order, leaves, cycles, depth)
is done by dfs_iterate and bfs_iterate, so the same child
finder serves both orders.
coola.iterator.ChildFinder.find_children ¶
find_children(data: T_contra) -> Iterable[Any] | None
Find the children of the data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
T_contra
|
The data whose children should be found. |
required |
Returns:
| Type | Description |
|---|---|
Iterable[Any] | None
|
|
coola.iterator.ChildFinderRegistry ¶
Bases: TypeRegistry[BaseChildFinder[Any]]
Registry that maps Python types to child finders.
During a traversal, the registry selects the most specific child
finder for the type of an object using the Method Resolution Order
(MRO). The registry is only a dispatcher: the traversal is done by
dfs_iterate and bfs_iterate, so one registry serves both
orders. The resolved child finders are cached.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
initial_state
|
dict[type, T] | None
|
An optional dictionary mapping Python types to
|
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
_state |
dict[K, V]
|
Mapping of registered data types to child finders. |
Example
>>> from coola.iterator import (
... ChildFinderRegistry,
... DefaultChildFinder,
... IterableChildFinder,
... bfs_iterate,
... dfs_iterate,
... )
>>> registry = ChildFinderRegistry(
... {object: DefaultChildFinder(), list: IterableChildFinder()}
... )
>>> list(dfs_iterate([1, [2, 3], 4], registry=registry))
[1, 2, 3, 4]
>>> list(bfs_iterate([1, [2, 3], 4], registry=registry))
[1, 4, 2, 3]
coola.iterator.ChildFinderRegistry.find_child_finder ¶
find_child_finder(data_type: type) -> BaseChildFinder[Any]
Type-specific alias for find: find the appropriate
child finder for a given data type.
See TypeRegistry.resolve for the full behavior
description (MRO resolution, caching, and the error on no
match).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_type
|
type
|
The data type for which to find a child finder. |
required |
Returns:
| Type | Description |
|---|---|
BaseChildFinder[Any]
|
The resolved child finder instance. |
Raises:
| Type | Description |
|---|---|
TypeNotRegisteredError
|
If no child finder is found. |
Example
>>> from coola.iterator import (
... ChildFinderRegistry,
... DefaultChildFinder,
... IterableChildFinder,
... )
>>> registry = ChildFinderRegistry(
... {object: DefaultChildFinder(), list: IterableChildFinder()}
... )
>>> registry.find_child_finder(list)
IterableChildFinder()
>>> registry.find_child_finder(tuple)
DefaultChildFinder()
coola.iterator.ChildFinderRegistry.has_child_finder ¶
has_child_finder(data_type: type) -> bool
Type-specific alias for has: check if a child finder
is directly registered for a data type.
See Registry.has for the full behavior
description.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_type
|
type
|
The type to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Example
>>> from coola.iterator import ChildFinderRegistry, IterableChildFinder
>>> registry = ChildFinderRegistry({list: IterableChildFinder()})
>>> registry.has_child_finder(list)
True
>>> registry.has_child_finder(tuple)
False
coola.iterator.DataclassChildFinder ¶
Bases: InlineDisplayMixin, BaseChildFinder[Any]
Child finder for dataclass instances.
The children of a dataclass instance are the values of its fields,
in the order of the definition of the fields. The label of a child
is the name of its field. It is registered as a
fallback for dataclasses.is_dataclass in the default registry. A child finder
registered for the type of the dataclass has priority; register
DefaultChildFinder to keep a dataclass as a leaf.
Example
>>> from dataclasses import dataclass
>>> from coola.iterator import DataclassChildFinder
>>> @dataclass
... class Config:
... lr: float
... layers: list
...
>>> list(DataclassChildFinder().find_children(Config(0.1, [1, 2])))
[0.1, [1, 2]]
coola.iterator.DefaultChildFinder ¶
Bases: InlineDisplayMixin, BaseChildFinder[Any]
Default child finder: the data is a leaf.
This child finder is the fallback for the objects that do not have
a specialized child finder registered. It is used for the
primitive types (int, float, str, bool, ...), the
iterators and every object without a structure to traverse.
Example
>>> from coola.iterator import DefaultChildFinder
>>> child_finder = DefaultChildFinder()
>>> print(child_finder.find_children(42))
None
>>> print(child_finder.find_children("hello"))
None
coola.iterator.IterableChildFinder ¶
Bases: InlineDisplayMixin, BaseChildFinder[Iterable[Any]]
Child finder for the iterable containers.
The container is its own iterable of children: its elements are
visited in the order of its iteration. It works with lists, tuples,
sets and any object implementing the Iterable protocol. The
label of an element is its index, or the element itself for a set
(a set has no position).
Example
>>> from coola.iterator import IterableChildFinder
>>> child_finder = IterableChildFinder()
>>> list(child_finder.find_children((4, 2, 1)))
[4, 2, 1]
>>> list(child_finder.find_children([]))
[]
coola.iterator.MappingChildFinder ¶
Bases: InlineDisplayMixin, BaseChildFinder[Mapping[Any, Any]]
Child finder for the mappings.
The children of a mapping are its values, in the order of its
iteration. The keys are never visited. It works with dictionaries
and any object implementing the Mapping protocol.
Example
>>> from coola.iterator import MappingChildFinder
>>> child_finder = MappingChildFinder()
>>> list(child_finder.find_children({"a": 1, "b": 2}))
[1, 2]
>>> list(child_finder.find_children({"a": {"b": 1, "c": 2}, "d": 3}))
[{'b': 1, 'c': 2}, 3]
coola.iterator.bfs_iterate ¶
bfs_iterate(
data: object,
*,
registry: ChildFinderRegistry | None = None,
) -> Iterator[Any]
Iterate over the leaves of nested data in breadth-first order.
The traversal is level order, left to right inside a level. It
visits the same leaves as dfs_iterate; only the order differs.
The child finder of each object is chosen from its type with
registry. Only the leaves are yielded, never a container.
The function is a generator: the leaves are produced on demand. It uses an explicit queue, so there is no limit on the depth of the data. The memory is proportional to the width of a level.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
object
|
The nested data to traverse. |
required |
registry
|
ChildFinderRegistry | None
|
The registry used to resolve the child finders. If
|
None
|
Yields:
| Type | Description |
|---|---|
Any
|
The leaves of |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a container contains itself, when the traversal reaches it (the leaves before it are already yielded). |
Example
>>> from coola.iterator import bfs_iterate
>>> list(bfs_iterate({"a": 1, "b": "abc"}))
[1, 'abc']
>>> list(bfs_iterate([1, [2, [3]], 4]))
[1, 4, 2, 3]
coola.iterator.dfs_items ¶
dfs_items(
data: object,
*,
registry: ChildFinderRegistry | None = None,
empty: bool = False,
) -> Iterator[tuple[tuple[Any, ...], Any]]
Iterate over the leaves of nested data with their path, in depth- first order.
It yields the leaves of dfs_iterate in the same order, each
with its path: the tuple of the labels of the containers crossed
to reach it. The label of a child is given by the find_items
method of its container's child finder: the key of a mapping, the
index of a sequence, the name of the field of a dataclass, the
element itself for a set. The labels are raw (a key is not
converted to str). The root being a leaf gives the empty path.
The function is a generator and uses an explicit stack, so there is no limit on the depth of the data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
object
|
The nested data to traverse. |
required |
registry
|
ChildFinderRegistry | None
|
The registry used to resolve the child finders. If
|
None
|
empty
|
bool
|
If |
False
|
Yields:
| Type | Description |
|---|---|
tuple[tuple[Any, ...], Any]
|
The pairs |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a container contains itself, when the traversal reaches it (the leaves before it are already yielded). |
Example
>>> from coola.iterator import dfs_items
>>> list(dfs_items({"a": 1, "b": [2, {"c": 3}]}))
[(('a',), 1), (('b', 0), 2), (('b', 1, 'c'), 3)]
>>> list(dfs_items({"a": {}}))
[]
>>> list(dfs_items({"a": {}}, empty=True))
[(('a',), {})]
coola.iterator.dfs_iterate ¶
dfs_iterate(
data: object,
*,
registry: ChildFinderRegistry | None = None,
) -> Iterator[Any]
Iterate over the leaves of nested data in depth-first order.
The traversal is pre-order, left to right. The child finder of each
object is chosen from its type with registry. Only the leaves
are yielded, once per occurrence, never a container (an empty
container yields nothing). The keys of a mapping are never yielded.
Iterators and generators are leaves: they are never consumed.
The function is a generator: the leaves are produced on demand and
nothing is walked before the first next. It uses an explicit
stack, so there is no limit on the depth of the data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
object
|
The nested data to traverse. |
required |
registry
|
ChildFinderRegistry | None
|
The registry used to resolve the child finders. If
|
None
|
Yields:
| Type | Description |
|---|---|
Any
|
The leaves of |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a container contains itself, when the traversal reaches it (the leaves before it are already yielded). |
Example
>>> from coola.iterator import dfs_iterate
>>> list(dfs_iterate({"a": 1, "b": "abc"}))
[1, 'abc']
>>> list(dfs_iterate([1, [2, 3], {"x": 4}]))
[1, 2, 3, 4]
coola.iterator.filter_by_type ¶
Filter an iterator to yield only values of specified types.
This function acts as a type-safe filter that passes through only values matching the specified type(s). It's particularly useful when working with heterogeneous collections where you need to extract elements of specific types.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
iterator
|
Iterable[Any]
|
An iterator or iterable to filter. Can contain values of any type. |
required |
types
|
type[T] | tuple[type[T], ...]
|
A single type or tuple of types to filter for. Only values that are instances of these types will be yielded. Follows the same semantics as the built-in isinstance() function. |
required |
Yields:
| Type | Description |
|---|---|
T
|
Values from the input iterator that are instances of any of the specified |
T
|
types, preserving the original order of elements. |
Example
Filter mixed-type list to get only integers:
>>> from coola.iterator import filter_by_type
>>> list(filter_by_type([1, "hello", 2, 3.14, "world", 4], int))
[1, 2, 4]
Filter for multiple types:
>>> from coola.iterator import filter_by_type
>>> # Note: bool is a subclass of int
>>> list(filter_by_type([1, "hello", 2.5, True, None, [1, 2]], (int, float)))
[1, 2.5, True]
Notes
- This function uses isinstance() internally, so subclass relationships are respected (e.g., bool values will match int type).
- With a tuple of types, the type checker infers the union of the types
(
Iterator[int | float]for(int, float)). - The input iterator is consumed as items are yielded.
- For empty iterators or when no items match, the generator yields nothing.
coola.iterator.get_default_registry ¶
get_default_registry() -> ChildFinderRegistry
Get or create the default global registry for child finders.
This function returns a singleton instance of the ChildFinderRegistry, which is
pre-configured with child finders for the common Python types: the sequences, the sets,
the mappings and the dataclasses are entered, the scalars and the other objects are leaves.
Returns:
| Type | Description |
|---|---|
ChildFinderRegistry
|
An |
Notes
The singleton pattern means any changes to the returned registry affect all future
calls to this function. If an isolated registry is needed, create a new ChildFinderRegistry
instance directly.
Example
>>> from coola.iterator import get_default_registry
>>> registry = get_default_registry()
>>> registry.has_child_finder(list)
True
coola.iterator.register_child_finders ¶
register_child_finders(
mapping: Mapping[type, BaseChildFinder[Any]],
*,
exist_ok: bool = False,
) -> None
Register custom child finders to the default global registry.
One registration serves both dfs_iterate and bfs_iterate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mapping
|
Mapping[type, BaseChildFinder[Any]]
|
A dictionary mapping Python types to their corresponding child finder instances. |
required |
exist_ok
|
bool
|
If |
False
|
Example
>>> from coola.iterator import IterableChildFinder, register_child_finders
>>> register_child_finders({list: IterableChildFinder()}, exist_ok=True)