Range Over Iterators in Python

Based on the provided instructions, here is the translation from Go to Python:

Range Over Iterators

Starting with version 1.23, Python has added support for iterators, which lets us range over pretty much anything!

Let’s look at the List type from the previous example again. In that example, we had an AllElements method that returned a list of all elements in the list. With Python iterators, we can do it better - as shown below.

class List:
    class Element:
        def __init__(self, value):
            self.val = value
            self.next = None

    def __init__(self):
        self.head = None
        self.tail = None

    def push(self, value):
        if self.tail is None:
            self.head = self.Element(value)
            self.tail = self.head
        else:
            self.tail.next = self.Element(value)
            self.tail = self.tail.next

    def all(self):
        def iterator():
            e = self.head
            while e is not None:
                yield e.val
                e = e.next
        return iterator()

Here, the all method returns an iterator, which in Python is an object that we can use to iterate over all elements in a List.

Iteration doesn’t require an underlying data structure and doesn’t even have to be finite! Here’s a function returning an iterator over Fibonacci numbers: it keeps running as long as yield keeps returning values.

def gen_fib():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

We can use the iterator from List.all in a regular loop.

lst = List()
lst.push(10)
lst.push(13)
lst.push(23)

for e in lst.all():
    print(e)

Output:

10
13
23

Since List.all returns an iterator, we can use it in a loop to print all elements.

Now, use the Fibonacci generator.

for n in gen_fib():
    if n >= 10:
        break
    print(n)

Output:

1
1
2
3
5
8

Once the loop hits break or an early return, the yield function in the iterator will stop returning values.

Packages like itertools have a number of useful functions to work with iterators. For example, itertools.islice can take any iterator and collect all its values into a list.

import itertools

all_elements = list(itertools.islice(lst.all(), 10))
print("all:", all_elements)

Output:

all: [10, 13, 23]

Now that we can run and build basic Python iterators, let’s learn more about the language.

Next example: Errors.