Search code examples
pythongeneratorpython-itertoolscyclestopiteration

Implementing my own Cycle function ( mocking itertools.cycle method)


Am new to Python and am trying to implement my own Cycle method mocking itertools.cycle method. I have written the following code. It gives the output, but with an exception.

  1. I want to understand why this exception is thrown and is there a way to avoid it
  2. Is there a better / alternative way to implement the cycle method
def cycle(iterinp):
    iter1 = iterinp
    iterVal = iter(iter1)
    while True:
        try:
            yield next(iterVal)
        except TypeError:
            print("Argument is not an iterator")
        except:
            iter1 = iterinp
            iterVal = iter(iter1)


c = cycle([1,2,3])
print(next(c))
print(next(c))
print(next(c))
print(next(c))
print(next(c))
print(next(c))

Output :

1 2
3
1
2
3

Exception : Exception ignored in: <generator object cycle at 0x0000016BECE19C40> RuntimeError: generator ignored GeneratorExit


Solution

  • When a program exits, it tries to perform cleanup by calling the __del__ method of any remaining objects. For generator objects the __del__ method would call their close method, which raises the GeneratorExit exception so that the generators would exit normally.

    But since your code blindly catches all exceptions with the except: block, the GeneratorExit exception has no way to propagate, causing the said RuntimeError complaining about GeneratorExit getting ignored.

    The solution is to simply be specific about the type of exception you actually want to catch while letting other exceptions propagate. In this case you really only want to catch StopIteration, so change:

    except:
    

    to:

    except StopIteration:
    

    Demo: https://replit.com/@blhsing/StainedFumblingOrganization

    Note that the __del__ method is not guaranteed to be always called even upon program exit, which is why @SuperStormer commented about not being able to reproduce the error in his/her attempt.

    Furthermore, itertools.cycle is supposed to take any iterable object as an argument, not just a list, but with your implementation, once the input iterable is exhausted, it cannot be restored with a simple call of iter unless it is a sequence such as a list as it happens to be the case with your example.

    itertools.cycle's documentation already has a perfectly fine example of a good Python implementation for your reference:

    Make an iterator returning elements from the iterable and saving a copy of each. When the iterable is exhausted, return elements from the saved copy. Repeats indefinitely. Roughly equivalent to:

    def cycle(iterable):
        # cycle('ABCD') --> A B C D A B C D A B C D ...
        saved = []
        for element in iterable:
            yield element
            saved.append(element)
        while saved:
            for element in saved:
                  yield element