简体   繁体   中英

recursion to reset a generator in python

I'm trying to write a function that returns the next element of a generator and if it is at the end of the generator it resets it and returns the next result. The expected output of the code below would be:

1
2
3
1
2

However that is not what I get obviously. What am I doing that is incorrect?

a = '123'

def convert_to_generator(iterable):
    return (x for x in iterable)

ag = convert_to_generator(a)

def get_next_item(gen, original):
    try:
        return next(gen)
    except StopIteration:
        gen = convert_to_generator(original)
        get_next_item(gen, original)

for n in range(5):
        print(get_next_item(ag,a))

 1
 2
 3
 None
 None

You need to return the result of your recursive call:

return get_next_item(gen, original)

which still does not make this a working approach. The generator ag used in your for-loop is not changed by the rebinding of the local variable gen in your function. It will stay exhausted...

As has been mentioned in the comments, check out itertools.cycle .

get_next_item is a generator, that returns an iterator, that gives you the values it yield s via the __next__ method. For that reason, your statement doesn't do anything.

What you want to do is this:

def get_next_item(gen, original):
    try:
        return next(gen)
    except StopIteration:
        gen = convert_to_generator(original)
        for i in get_next_item(gen, original):
            return i

or shorter, and completely equivalent (as long as gen has a __iter__ method, which it probably has):

def get_next_item(gen, original):
    for i in gen:
        yield i
    for i in get_next_item(convert_to_generator(original)):
        yield i

Or without recursion (which is a big problem in python, as it is 1. limited in depth and 2. slow):

def get_next_item(gen, original):
    for i in gen:
        yield i
    while True:
        for i in convert_to_generator(original):
            yield i

If convert_to_generator is just a call to iter , it is even shorter:

def get_next_item(gen, original):
     for i in gen:
         yield i
     while True:
         for i in original:
             yield i

or, with itertools :

import itertools

def get_next_item(gen, original):
    return itertools.chain(gen, itertools.cycle(original))

and get_next_item is equivalent to itertools.cycle if gen is guaranteed to be an iterator for original .

Side note: You can exchange for i in x: yield i for yield from x (where x is some expression) with Python 3.3 or higher.

itertools.cycle(iterable)是否可能替代?

the easy way is just use itertools.cycle , otherwise you would need to remember the elements in the iterable if said iterable is an iterator (aka a generator) becase those can't be reset, if its not a iterator, you can reuse it many times.

the documentation include a example implementation

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

or for example, to do the reuse thing

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

example use

test = cycle("123")
for i in range(5):
    print(next(test))    

now about your code, the problem is simple, it don't remember it state

def get_next_item(gen, original):
    try:
        return next(gen)
    except StopIteration:
        gen = convert_to_generator(original)  # <-- the problem is here
        get_next_item(gen, original)          #and you should return something here

in the marked line a new generator is build, but you would need to update your ag variable outside this function to get the desire behavior, there are ways to do it, like changing your function to return the element and the generator, there are other ways, but they are not recommended or more complicated like building a class so it remember its state

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM