简体   繁体   English

递归以重置python中的生成器

[英]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. for循环中使用的生成器ag不会因函数中局部变量gen的重新绑定而改变。 It will stay exhausted... 它将筋疲力尽...

As has been mentioned in the comments, check out itertools.cycle . 正如评论中提到的,请查看itertools.cycle

get_next_item is a generator, that returns an iterator, that gives you the values it yield s via the __next__ method. get_next_item是一个生成器,它返回一个迭代器,该迭代器通过__next__方法为您提供它yield的值。 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): 或更短,并且完全等效(只要gen可能具有__iter__方法):

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): 或不进行递归(这在python中是一个大问题,因为它的限制是1.深度有限且2.缓慢):

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: 如果convert_to_generator只是对iter的调用,它会更短:

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

or, with itertools : 或者,使用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 . 如果保证gen可以作为original的迭代器,则get_next_item等同于itertools.cycle。

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. 旁注:您可以for i in x: yield i交换for i in x: yield i表示使用Python 3.3或更高版本yield from x (其中x是某些表达式) yield from x

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. 最简单的方法是使用itertools.cycle ,否则,如果说iterable是一个迭代器(又称生成器),则需要记住iterable中的元素,以防无法重置,如果它不是迭代器,则可以重用它多次。

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 在标记的行中,构建了一个新的生成器,但是您需要在此函数之外更新ag变量以获得所需的行为,有多种方法可以完成此操作,例如更改函数以返回元素和生成器,还有其他方法方式,但不建议使用它们,或更不建议像构建类那样复杂,这样它就可以记住其状态

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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