Skip to content

PyoIterable

Bases: Checkable, Fluent, Protocol


              flowchart TD
              pyochain.abc._iterable.PyoIterable[PyoIterable]
              pyochain.abc._mixins.Checkable[Checkable]
              pyochain.abc._mixins.Fluent[Fluent]
              pyochain.abc._mixins.Pipe[Pipe]
              pyochain.abc._mixins.Tap[Tap]

                              pyochain.abc._mixins.Checkable --> pyochain.abc._iterable.PyoIterable
                
                pyochain.abc._mixins.Fluent --> pyochain.abc._iterable.PyoIterable
                                pyochain.abc._mixins.Pipe --> pyochain.abc._mixins.Fluent
                
                pyochain.abc._mixins.Tap --> pyochain.abc._mixins.Fluent
                



              click pyochain.abc._iterable.PyoIterable href "" "pyochain.abc._iterable.PyoIterable"
              click pyochain.abc._mixins.Checkable href "" "pyochain.abc._mixins.Checkable"
              click pyochain.abc._mixins.Fluent href "" "pyochain.abc._mixins.Fluent"
              click pyochain.abc._mixins.Pipe href "" "pyochain.abc._mixins.Pipe"
              click pyochain.abc._mixins.Tap href "" "pyochain.abc._mixins.Tap"
            

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 Fluent 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"])

assert not registry.iter().all(lambda name: name.startswith("A"))
assert registry.iter().join(", ") == "Alice, Bob, Charlie"
assert registry.iter().map(str.lower).join(", ") == "alice, bob, charlie"

x = (
    registry
    .ok_or("Registry is empty")
    .map(lambda s: s.iter().join(", "))
    .unwrap()
)
assert x == ("Alice, Bob, Charlie")
Source code in pyochain/abc/_iterable.pyi
 7
 8
 9
10
11
12
13
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
@runtime_checkable
class PyoIterable[T](Checkable, Fluent, Protocol):
    """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 `Fluent` 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"])

        assert not registry.iter().all(lambda name: name.startswith("A"))
        assert registry.iter().join(", ") == "Alice, Bob, Charlie"
        assert registry.iter().map(str.lower).join(", ") == "alice, bob, charlie"

        x = (
            registry
            .ok_or("Registry is empty")
            .map(lambda s: s.iter().join(", "))
            .unwrap()
        )
        assert x == ("Alice, Bob, Charlie")
        ```
    """
    @abstractmethod
    def __iter__(self) -> Iterator[T]: ...
    def iter[I](self: PyoIterable[I]) -> PyoIterator[I]:
        """Returns a `PyoIterator` object over the `Iterable`.

        By default, this returns an `Iter`, but can be overriden by concrete subclasses.

        This method is the pyochain equivalent of the `__iter__` dunder method.

        Returns:
            PyoIterator[I]: An `Iterator` over the `Iterable`.

        Example:
            ```python
            from pyochain import Seq

            seq = Seq(1, 2, 3)
            iterator = seq.iter()

            assert iterator.collect(Seq) == Seq(1, 2, 3)

            # iterator is now empty
            assert iterator.collect(Seq).is_empty()
            assert iterator.next().is_none()
            ```
        """

iter()

Returns a PyoIterator object over the Iterable.

By default, this returns an Iter, but can be overriden by concrete subclasses.

This method is the pyochain equivalent of the __iter__ dunder method.

Returns:

Type Description
PyoIterator[I]

PyoIterator[I]: An Iterator over the Iterable.

Example
from pyochain import Seq

seq = Seq(1, 2, 3)
iterator = seq.iter()

assert iterator.collect(Seq) == Seq(1, 2, 3)

# iterator is now empty
assert iterator.collect(Seq).is_empty()
assert iterator.next().is_none()
Source code in pyochain/abc/_iterable.pyi
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def iter[I](self: PyoIterable[I]) -> PyoIterator[I]:
    """Returns a `PyoIterator` object over the `Iterable`.

    By default, this returns an `Iter`, but can be overriden by concrete subclasses.

    This method is the pyochain equivalent of the `__iter__` dunder method.

    Returns:
        PyoIterator[I]: An `Iterator` over the `Iterable`.

    Example:
        ```python
        from pyochain import Seq

        seq = Seq(1, 2, 3)
        iterator = seq.iter()

        assert iterator.collect(Seq) == Seq(1, 2, 3)

        # iterator is now empty
        assert iterator.collect(Seq).is_empty()
        assert iterator.next().is_none()
        ```
    """