Skip to content

Iter

Bases: PyoIterator[T]


              flowchart TD
              pyochain._iter.Iter[Iter]
              pyochain.abc._iterator.PyoIterator[PyoIterator]
              pyochain.abc._iterable.PyoIterable[PyoIterable]
              pyochain.rs.Fluent[Fluent]
              pyochain.rs.Pipe[Pipe]
              pyochain.rs.Tap[Tap]
              pyochain.rs.Checkable[Checkable]

                              pyochain.abc._iterator.PyoIterator --> pyochain._iter.Iter
                                pyochain.abc._iterable.PyoIterable --> pyochain.abc._iterator.PyoIterator
                                pyochain.rs.Fluent --> pyochain.abc._iterable.PyoIterable
                                pyochain.rs.Pipe --> pyochain.rs.Fluent
                
                pyochain.rs.Tap --> pyochain.rs.Fluent
                

                pyochain.rs.Checkable --> pyochain.abc._iterable.PyoIterable
                




              click pyochain._iter.Iter href "" "pyochain._iter.Iter"
              click pyochain.abc._iterator.PyoIterator href "" "pyochain.abc._iterator.PyoIterator"
              click pyochain.abc._iterable.PyoIterable href "" "pyochain.abc._iterable.PyoIterable"
              click pyochain.rs.Fluent href "" "pyochain.rs.Fluent"
              click pyochain.rs.Pipe href "" "pyochain.rs.Pipe"
              click pyochain.rs.Tap href "" "pyochain.rs.Tap"
              click pyochain.rs.Checkable href "" "pyochain.rs.Checkable"
            

Concrete implementation for abc::PyoIterator.

Can be instantiated from any Iterable (like lists, sets, generators, etc.) efficiently (it only calls the builtin iter() on the input).

As such, creating an Iter from an Iterator is virtually free.

Tip

Iter::__iter__() returns the underlying wrapped Iterator, hence native speed is kept.

i.e Iter([...]).map(f).collect(list) is as fast as list(map(f, [...])).

Parameters:

Name Type Description Default
data Iterable[T]

Any object that can be iterated over.

required
See Also

abc::PyoIterator: The abstract base class that Iter implements.

Example

>>> from pyochain import Iter, Seq
>>>
>>> data = (0, 1, 2, 3, 4)
>>> Iter(data).collect(Seq)
Seq(0, 1, 2, 3, 4)
>>> iterator = Iter(data)
>>> # First we have a tuple iterator
>>> iterator._inner.__class__.__name__
'tuple_iterator'
>>> # Now we have a map object
>>> mapped = iterator.map(lambda x: x * 2)
>>> mapped._inner.__class__.__name__
'map'
>>> # We collect it, by default into a Seq
>>> mapped.collect(Seq)
Seq(0, 2, 4, 6, 8)
>>> # iterator is now exhausted
>>> iterator.collect(Seq)
Seq()
You can also easily create an Iter from a generator expression:
>>> from pyochain import Iter
>>> gen_expr = (x * x for x in range(5))
>>> Iter(gen_expr).collect(Seq)
Seq(0, 1, 4, 9, 16)
Or from a generator function:
>>> from pyochain import Iter
>>> def gen_func():
...     for x in range(5):
...         yield x * x
>>>
>>> Iter(gen_func()).collect(Seq)
Seq(0, 1, 4, 9, 16)

Source code in src/pyochain/_iter.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
class Iter[T](PyoIterator[T]):
    """Concrete implementation for `abc::PyoIterator`.

    Can be instantiated from any `Iterable` (like lists, sets, generators, etc.) efficiently (it only calls the builtin `iter()` on the input).

    As such, creating an `Iter` from an `Iterator` is virtually free.

    Tip:
        `Iter::__iter__()` returns the underlying wrapped `Iterator`, hence native speed is kept.

        i.e `Iter([...]).map(f).collect(list)` is as fast as `list(map(f, [...]))`.

    Args:
        data (Iterable[T]): Any object that can be iterated over.

    See Also:
        [`abc::PyoIterator`][PyoIterator]: The abstract base class that `Iter` implements.

    Example:
        ```python
        >>> from pyochain import Iter, Seq
        >>>
        >>> data = (0, 1, 2, 3, 4)
        >>> Iter(data).collect(Seq)
        Seq(0, 1, 2, 3, 4)
        >>> iterator = Iter(data)
        >>> # First we have a tuple iterator
        >>> iterator._inner.__class__.__name__
        'tuple_iterator'
        >>> # Now we have a map object
        >>> mapped = iterator.map(lambda x: x * 2)
        >>> mapped._inner.__class__.__name__
        'map'
        >>> # We collect it, by default into a Seq
        >>> mapped.collect(Seq)
        Seq(0, 2, 4, 6, 8)
        >>> # iterator is now exhausted
        >>> iterator.collect(Seq)
        Seq()

        ```
        You can also easily create an `Iter` from a generator expression:
        ```python
        >>> from pyochain import Iter
        >>> gen_expr = (x * x for x in range(5))
        >>> Iter(gen_expr).collect(Seq)
        Seq(0, 1, 4, 9, 16)

        ```
        Or from a generator function:
        ```python
        >>> from pyochain import Iter
        >>> def gen_func():
        ...     for x in range(5):
        ...         yield x * x
        >>>
        >>> Iter(gen_func()).collect(Seq)
        Seq(0, 1, 4, 9, 16)

        ```
    """

    _inner: Iterator[T]
    __slots__ = ("_inner",)  # pyright: ignore[reportUnannotatedClassAttribute, reportIncompatibleUnannotatedOverride]

    def __init__(self, data: Iterable[T]) -> None:
        self._inner = iter(data)

    @override
    def __iter__(self) -> Iterator[T]:
        return self._inner

    @override
    def __next__(self) -> T:
        return next(self._inner)

    def __bool__(self) -> bool:
        """Check if the `Iterator` has at least one element (mutates **self**).

        After calling this, the `Iterator` still contains all elements.

        Returns:
            bool: True if the `Iterator` has at least one element, False otherwise.

        Example:
            ```python
            >>> from pyochain import Iter, Seq
            >>> it = Iter((1, 2, 3))
            >>> bool(it)
            True
            >>> it.collect(Seq)  # All elements still available
            Seq(1, 2, 3)

            ```
        """
        match next(self._inner, BOOL_SENTINEL):
            case sentinel if sentinel is BOOL_SENTINEL:
                return False
            case some_val:
                self._inner = itertools.chain((some_val,), self._inner)  # pyright: ignore[reportAttributeAccessIssue]
                return True

    @override
    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self._inner.__repr__()})"

    @classmethod
    def from_ref(cls, other: Self) -> Self:
        """Create an independent lazy copy from another `Iter`.

        Both the original and the returned `Iter` can be consumed independently, in a lazy manner.

        Note:
            Values consumed by one iterator remain in the shared buffer until the other iterator consumes them too.

            This is the unavoidable cost of having two independent iterators over the same source.

            However, once both iterators have passed a value, it's freed from memory.

        See Also:
            - [`Iter::cloned`][cloned] which is the instance method version of this function.

        Args:
            other (Self): An `Iter` instance to copy.

        Returns:
            Self: A new `Iter` instance that is independent from the original.

        Example:
            ```python
            >>> from pyochain import Iter, Seq
            >>> original = Iter((1, 2, 3))
            >>> copy = Iter.from_ref(original)
            >>> copy.map(lambda x: x * 2).collect(Seq)
            Seq(2, 4, 6)
            >>> original.next()
            Some(1)

            ```
        """
        it1, it2 = itertools.tee(other._inner)
        other._inner = it1
        return cls(it2)

    def cloned(self) -> PyoIterator[T]:
        """Clone the `Iter` into a new independent `Iter` using `itertools.tee`.

        After calling this method, the original `Iter` will continue to yield elements independently of the cloned one.

        Note:
            Values consumed by one iterator remain in the shared buffer until the other iterator consumes them too.

            This is the unavoidable cost of having two independent iterators over the same source.

            However, once both iterators have passed a value, it's freed from memory.

        Returns:
            PyoIterator[T]: A new independent cloned iterator.

        Example:
            ```python
            >>> from pyochain import Iter, Seq
            >>> it = Iter((1, 2, 3))
            >>> cloned = it.cloned()
            >>> cloned.collect(Seq)
            Seq(1, 2, 3)
            >>> it.collect(Seq)
            Seq(1, 2, 3)

            ```
        """
        it1, it2 = itertools.tee(self._inner)
        self._inner = it1
        return self._from_iterable(it2)

__bool__()

Check if the Iterator has at least one element (mutates self).

After calling this, the Iterator still contains all elements.

Returns:

Name Type Description
bool bool

True if the Iterator has at least one element, False otherwise.

Example
>>> from pyochain import Iter, Seq
>>> it = Iter((1, 2, 3))
>>> bool(it)
True
>>> it.collect(Seq)  # All elements still available
Seq(1, 2, 3)
Source code in src/pyochain/_iter.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def __bool__(self) -> bool:
    """Check if the `Iterator` has at least one element (mutates **self**).

    After calling this, the `Iterator` still contains all elements.

    Returns:
        bool: True if the `Iterator` has at least one element, False otherwise.

    Example:
        ```python
        >>> from pyochain import Iter, Seq
        >>> it = Iter((1, 2, 3))
        >>> bool(it)
        True
        >>> it.collect(Seq)  # All elements still available
        Seq(1, 2, 3)

        ```
    """
    match next(self._inner, BOOL_SENTINEL):
        case sentinel if sentinel is BOOL_SENTINEL:
            return False
        case some_val:
            self._inner = itertools.chain((some_val,), self._inner)  # pyright: ignore[reportAttributeAccessIssue]
            return True

cloned()

Clone the Iter into a new independent Iter using itertools.tee.

After calling this method, the original Iter will continue to yield elements independently of the cloned one.

Note

Values consumed by one iterator remain in the shared buffer until the other iterator consumes them too.

This is the unavoidable cost of having two independent iterators over the same source.

However, once both iterators have passed a value, it's freed from memory.

Returns:

Type Description
PyoIterator[T]

PyoIterator[T]: A new independent cloned iterator.

Example
>>> from pyochain import Iter, Seq
>>> it = Iter((1, 2, 3))
>>> cloned = it.cloned()
>>> cloned.collect(Seq)
Seq(1, 2, 3)
>>> it.collect(Seq)
Seq(1, 2, 3)
Source code in src/pyochain/_iter.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def cloned(self) -> PyoIterator[T]:
    """Clone the `Iter` into a new independent `Iter` using `itertools.tee`.

    After calling this method, the original `Iter` will continue to yield elements independently of the cloned one.

    Note:
        Values consumed by one iterator remain in the shared buffer until the other iterator consumes them too.

        This is the unavoidable cost of having two independent iterators over the same source.

        However, once both iterators have passed a value, it's freed from memory.

    Returns:
        PyoIterator[T]: A new independent cloned iterator.

    Example:
        ```python
        >>> from pyochain import Iter, Seq
        >>> it = Iter((1, 2, 3))
        >>> cloned = it.cloned()
        >>> cloned.collect(Seq)
        Seq(1, 2, 3)
        >>> it.collect(Seq)
        Seq(1, 2, 3)

        ```
    """
    it1, it2 = itertools.tee(self._inner)
    self._inner = it1
    return self._from_iterable(it2)

from_ref(other) classmethod

Create an independent lazy copy from another Iter.

Both the original and the returned Iter can be consumed independently, in a lazy manner.

Note

Values consumed by one iterator remain in the shared buffer until the other iterator consumes them too.

This is the unavoidable cost of having two independent iterators over the same source.

However, once both iterators have passed a value, it's freed from memory.

See Also
  • Iter::cloned which is the instance method version of this function.

Parameters:

Name Type Description Default
other Self

An Iter instance to copy.

required

Returns:

Name Type Description
Self Self

A new Iter instance that is independent from the original.

Example
>>> from pyochain import Iter, Seq
>>> original = Iter((1, 2, 3))
>>> copy = Iter.from_ref(original)
>>> copy.map(lambda x: x * 2).collect(Seq)
Seq(2, 4, 6)
>>> original.next()
Some(1)
Source code in src/pyochain/_iter.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
@classmethod
def from_ref(cls, other: Self) -> Self:
    """Create an independent lazy copy from another `Iter`.

    Both the original and the returned `Iter` can be consumed independently, in a lazy manner.

    Note:
        Values consumed by one iterator remain in the shared buffer until the other iterator consumes them too.

        This is the unavoidable cost of having two independent iterators over the same source.

        However, once both iterators have passed a value, it's freed from memory.

    See Also:
        - [`Iter::cloned`][cloned] which is the instance method version of this function.

    Args:
        other (Self): An `Iter` instance to copy.

    Returns:
        Self: A new `Iter` instance that is independent from the original.

    Example:
        ```python
        >>> from pyochain import Iter, Seq
        >>> original = Iter((1, 2, 3))
        >>> copy = Iter.from_ref(original)
        >>> copy.map(lambda x: x * 2).collect(Seq)
        Seq(2, 4, 6)
        >>> original.next()
        Some(1)

        ```
    """
    it1, it2 = itertools.tee(other._inner)
    other._inner = it1
    return cls(it2)