pythoniteratorcircular-list

Python: creating a class that could be iterated circularly


I want to create, in Python, a class behaving like a list but that could be iterated circularly use case example:

myc = SimpleCircle()
print(len(myc))
j = iter(myc)
for i in range (0, 5):
    print(next(j))

it will print a b c d a

the code I tried so far is the one below I know the issue is with my __next__

method which by the way seems ignored, I can use next even if I don't implement it

class SimpleCircle:
    def __init__(self):
        self._circle = ['a', 'b', 'c', 'd']
        self._l = iter(self._circle)


    def __len__(self):
        return len(self._circle)

    def __iter__(self):
        return (elem for elem in self._circle)

    def __next__(self):
        try:
            elem = next(self._l)
            idx = self._circle.index(elem)
            if idx < len(self._circle):
                return elem
            else:
                return self._circle[0]
        except StopIteration:
            pass

Solution

  • This actually already exists with itertools.cycle, for example:

    from itertools import cycle
    
    for x in cycle(['a', 'b', 'c', 'd']):
        print(x)
    

    will keep repeating the element.

    Next you here mix up the iterable, and the iterator, those are frequently different things.

    As an iterable we can keep iterating from self._circle:

    class SimpleCircle:
        def __init__(self):
            self._circle = ['a', 'b', 'c', 'd']
    
        def __len__(self):
            return len(self._circle)
    
        def __iter__(self):
            if not self._circle:
                raise StopIteration
            while True:
                yield from self._circle

    Or for an iterator:

    class CycleIterator:
    
        def __init__(self, iterable):
            self.iterator = iter(iterable)
            self.__next__ = self._iternext
            self.idx = 0
            self.list = []
    
        def _iternext(self):
            try:
                x = next(self.iterator)
                self.list.append(x)
                return x
            except StopIteration:
                self.__next__ = self._iterlist
                return self._iterlist()
    
        def _iterlist(self):
            try:
                return self.list[self.index]
            except IndexError:
                raise StopIteration
            finally:
                self.index = (self.index + 1) % len(self.list)