Skip to content

PyoIterable

Bases: Pipeable, Checkable, Iterable[T], ABC


              flowchart TD
              pyochain.abc._iterable.PyoIterable[PyoIterable]
              pyochain.rs.Pipeable[Pipeable]
              pyochain.rs.Into[Into]
              pyochain.rs.Inspect[Inspect]
              pyochain.rs.Checkable[Checkable]

                              pyochain.rs.Pipeable --> pyochain.abc._iterable.PyoIterable
                                pyochain.rs.Into --> pyochain.rs.Pipeable
                
                pyochain.rs.Inspect --> pyochain.rs.Pipeable
                

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


              click pyochain.abc._iterable.PyoIterable href "" "pyochain.abc._iterable.PyoIterable"
              click pyochain.rs.Pipeable href "" "pyochain.rs.Pipeable"
              click pyochain.rs.Into href "" "pyochain.rs.Into"
              click pyochain.rs.Inspect href "" "pyochain.rs.Inspect"
              click pyochain.rs.Checkable href "" "pyochain.rs.Checkable"
            

Base ABC for all pyochain Iterables.

It's the common API surface shared by:

  • eager Collections: Seq, Vec, Set, SetMut, Dict
  • lazy Iterator: Iter

It extends the standard Iterable[T] protocol, as well as Pipeable and Checkable.

All concrete subclasses must implement __iter__().

Note

The difference between an Iterable and an Iterator is often misunderstood, but it's actually quite simple.

An Iterable is any object that can create an Iterator.

It's sole responsbility is to provide an __iter__ method.

This method must return an object that have a __next__ method, which is the actual Iterator.

An Iterator is an object that can produce elements one at a time, and can be exhausted.

When you do a for x in my_iterable, Python implicitly calls my_iterable.__iter__(), and then repeatedly callsnext()on the resultingIterator` to get the elements.

More concretely, a list, for example, is an Iterable.

You can't call next() on a list, because it don't know how to produce elements by itself, it's primary responsibility being to store them.

However, as soon as you call map(my_list), [x for x in my_list], (*my_list), or any other operation that needs to visit elements, an Iterator is created (implicitly or explicitly) from the list.

It's also why abc::Iterator::__iter__ returns Self by convention.

Example

Since it's very straightforward to implement, it can very easily be integrated into business logic classes to provide them with a rich set of methods for free.

>>> from pyochain.abc import PyoIterable
>>> from dataclasses import dataclass
>>>
>>> @dataclass(slots=True)
... class ClientRegistry(PyoIterable[str]):
...     clients: list[str]
...
...     def __iter__(self):
...         return iter(self.clients)
>>>
>>> registry = ClientRegistry(["Alice", "Bob", "Charlie"])
>>> registry.iter().all(lambda name: name.startswith("A"))
False
>>> registry.iter().join(", ")
'Alice, Bob, Charlie'
>>> registry.iter().map(str.lower).join(", ")
'alice, bob, charlie'
>>> registry.ok_or("Registry is empty").map(lambda s: s.iter().join(", "))
Ok('Alice, Bob, Charlie')
Source code in src/pyochain/abc/_iterable.py
 14
 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
class PyoIterable[T](Pipeable, Checkable, Iterable[T], ABC):
    """Base ABC for all pyochain `Iterables`.

    It's the common API surface shared by:

    - eager `Collections`: `Seq`, `Vec`, `Set`, `SetMut`, `Dict`
    - lazy `Iterator`: `Iter`

    It extends the standard `Iterable[T]` protocol, as well as `Pipeable` and `Checkable`.

    All concrete subclasses must implement `__iter__()`.

    Note:
        The difference between an `Iterable` and an `Iterator` is often misunderstood, but it's actually quite simple.

        An `Iterable` is any object that can **create** an `Iterator`.

        It's sole responsbility is to provide an `__iter__` method.

        This method must return an object that have a `__next__` method, which is the actual `Iterator`.

        An `Iterator` is an object that can produce elements one at a time, and can be exhausted.

        When you do a `for x in my_iterable`, Python implicitly calls `my_iterable.__iter__(), and then repeatedly calls `next()` on the resulting `Iterator` to get the elements.

        More concretely, a `list`, for example, is an `Iterable`.

        You can't call `next()` on a `list`, because it don't know how to produce elements by itself, it's primary responsibility being to **store** them.

        However, as soon as you call `map(my_list)`, `[x for x in my_list]`, (*my_list), or any other operation that needs to visit elements, an `Iterator` is created (implicitly or explicitly) from the `list`.

        It's also why `abc::Iterator::__iter__` returns `Self` by convention.

    Example:
        Since it's very straightforward to implement, it can very easily be integrated into business logic classes to provide them with a rich set of methods for free.

        ```python
        >>> from pyochain.abc import PyoIterable
        >>> from dataclasses import dataclass
        >>>
        >>> @dataclass(slots=True)
        ... class ClientRegistry(PyoIterable[str]):
        ...     clients: list[str]
        ...
        ...     def __iter__(self):
        ...         return iter(self.clients)
        >>>
        >>> registry = ClientRegistry(["Alice", "Bob", "Charlie"])
        >>> registry.iter().all(lambda name: name.startswith("A"))
        False
        >>> registry.iter().join(", ")
        'Alice, Bob, Charlie'
        >>> registry.iter().map(str.lower).join(", ")
        'alice, bob, charlie'
        >>> registry.ok_or("Registry is empty").map(lambda s: s.iter().join(", "))
        Ok('Alice, Bob, Charlie')

        ```
    """

    # pyrefly: ignore [implicit-any-attribute]
    __slots__ = ()  # pyright: ignore[reportUnannotatedClassAttribute]

    def iter(self) -> Iter[T]:
        """Get an `Iter` over the `Iterable`.

        Call this to switch to lazy evaluation.

        Note:
            Calling this method on a class who is itself an `Iterator` has no effect.

        Returns:
            Iter[T]: An `Iterator` over the `Iterable`. The element type is inferred from the actual subclass.

        Example:
            ```python
            >>> from pyochain import Seq
            >>> seq = Seq((1, 2, 3))
            >>> iterator = seq.iter()
            >>> iterator.collect()
            Seq(1, 2, 3)
            >>> # iterator is now empty
            >>> iterator.collect()
            Seq()

            ```
        """
        from .._iter import Iter

        return Iter(iter(self))

    def first(self) -> T:
        """Return the first element of the `Iterable`.

        By default, this method convert the `Iterable` to an `Iterator` and returns the first element by calling `next()` on it.

        On `PyoSequence` and its subclasses (`Seq`, `Range`, etc.), this is overriden to directly use an efficient `__getitem__` access.

        If you already are using an `Iter`, prefer `Iter.next()` instead, which returns an `Option[T]` to handle exhaustion gracefully.

        Returns:
            T: The first element of the `Iterable`.

        Example:
            ```python
            >>> from pyochain import Seq
            >>> data = Seq((1, 2))
            >>> data.first()
            1
            >>> iterator = data.iter()
            >>> iterator.first()
            1
            >>> iterator.first()
            2
            >>> # iterator is now empty, using first again would raise an error
            >>> iterator.next()
            NONE

            ```
        """
        return next(iter(self))

    def last(self) -> T:
        """Return the last element of the `Iterable`.

        This is similar to `__getitem__` but works on lazy `Iterators`.

        Returns:
            T: The last element of the `Iterable`.

        Example:
            ```python
            >>> from pyochain import Seq
            >>> Seq((7, 8, 9)).last()
            9

            ```
        """
        return tls.last(iter(self))

first()

Return the first element of the Iterable.

By default, this method convert the Iterable to an Iterator and returns the first element by calling next() on it.

On PyoSequence and its subclasses (Seq, Range, etc.), this is overriden to directly use an efficient __getitem__ access.

If you already are using an Iter, prefer Iter.next() instead, which returns an Option[T] to handle exhaustion gracefully.

Returns:

Name Type Description
T T

The first element of the Iterable.

Example
>>> from pyochain import Seq
>>> data = Seq((1, 2))
>>> data.first()
1
>>> iterator = data.iter()
>>> iterator.first()
1
>>> iterator.first()
2
>>> # iterator is now empty, using first again would raise an error
>>> iterator.next()
NONE
Source code in src/pyochain/abc/_iterable.py
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
def first(self) -> T:
    """Return the first element of the `Iterable`.

    By default, this method convert the `Iterable` to an `Iterator` and returns the first element by calling `next()` on it.

    On `PyoSequence` and its subclasses (`Seq`, `Range`, etc.), this is overriden to directly use an efficient `__getitem__` access.

    If you already are using an `Iter`, prefer `Iter.next()` instead, which returns an `Option[T]` to handle exhaustion gracefully.

    Returns:
        T: The first element of the `Iterable`.

    Example:
        ```python
        >>> from pyochain import Seq
        >>> data = Seq((1, 2))
        >>> data.first()
        1
        >>> iterator = data.iter()
        >>> iterator.first()
        1
        >>> iterator.first()
        2
        >>> # iterator is now empty, using first again would raise an error
        >>> iterator.next()
        NONE

        ```
    """
    return next(iter(self))

iter()

Get an Iter over the Iterable.

Call this to switch to lazy evaluation.

Note

Calling this method on a class who is itself an Iterator has no effect.

Returns:

Type Description
Iter[T]

Iter[T]: An Iterator over the Iterable. The element type is inferred from the actual subclass.

Example
>>> from pyochain import Seq
>>> seq = Seq((1, 2, 3))
>>> iterator = seq.iter()
>>> iterator.collect()
Seq(1, 2, 3)
>>> # iterator is now empty
>>> iterator.collect()
Seq()
Source code in src/pyochain/abc/_iterable.py
 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
def iter(self) -> Iter[T]:
    """Get an `Iter` over the `Iterable`.

    Call this to switch to lazy evaluation.

    Note:
        Calling this method on a class who is itself an `Iterator` has no effect.

    Returns:
        Iter[T]: An `Iterator` over the `Iterable`. The element type is inferred from the actual subclass.

    Example:
        ```python
        >>> from pyochain import Seq
        >>> seq = Seq((1, 2, 3))
        >>> iterator = seq.iter()
        >>> iterator.collect()
        Seq(1, 2, 3)
        >>> # iterator is now empty
        >>> iterator.collect()
        Seq()

        ```
    """
    from .._iter import Iter

    return Iter(iter(self))

last()

Return the last element of the Iterable.

This is similar to __getitem__ but works on lazy Iterators.

Returns:

Name Type Description
T T

The last element of the Iterable.

Example
>>> from pyochain import Seq
>>> Seq((7, 8, 9)).last()
9
Source code in src/pyochain/abc/_iterable.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def last(self) -> T:
    """Return the last element of the `Iterable`.

    This is similar to `__getitem__` but works on lazy `Iterators`.

    Returns:
        T: The last element of the `Iterable`.

    Example:
        ```python
        >>> from pyochain import Seq
        >>> Seq((7, 8, 9)).last()
        9

        ```
    """
    return tls.last(iter(self))