简体   繁体   English

递归python生成器:为什么需要对yield进行迭代?

[英]Recursive python generators: why does the yield need to be iterated over?

A good exercise to test one's understanding of recursion is to write a function that generates all permutations of a string: 测试一个人对递归的理解的一个很好的练习是编写一个函数,该函数生成字符串的所有排列:

def get_perms(to_go, so_far=''):
    if not to_go:
        return [so_far]
    else:
        ret = []
        for i in range(len(to_go)):
            ret += get_perms(to_go[:i] + to_go[i+1:], so_far + to_go[i])
    return ret 

This code is fine, but we can significantly improve the efficiency from a memory standpoint by using a generator: 这段代码很好,但是从生成器的角度来看,我们可以通过使用生成器来显着提高效率:

def perms_generator(to_go, so_far=''):
    if not to_go:
        yield so_far
    else:
        for i in range(len(to_go)):
            for perm in perms_generator(to_go[:i] + to_go[i+1:], so_far + to_go[i]):
                yield perm

(Note: the last for loop can also be replaced with yield from in python 3.3) (注意:最后一个for循环也可以用python 3.3中的yield from代替)

My question: why do we need to iterate over the results of each recursive call? 我的问题:为什么我们需要迭代每个递归调用的结果? I know that yield returns a generator, but from the statement yield so_far it would seem as though we're getting a string, and not something we would need to iterate over. 我知道yield返回一个生成器,但是从yield so_far语句yield so_far ,好像我们正在获取字符串,而不是需要迭代的内容。 Rather, it would seem as though we could replace 相反,似乎我们可以取代

for perm in perms_generator(to_go[:i] + to_go[i+1:], so_far + to_go[i]):
    yield perm

with

yield perms_generator(to_go[:i] + to_go[i+1:], so_far + to_go[i])

Thank you. 谢谢。 Please let me know if the title is unclear. 如果标题不清楚,请告诉我。 I have a feeling the content of this question is related to this SO question . 我感觉这个问题的内容与此SO问题有关

Remember, any function using yield does not return those values to the caller. 请记住,任何使用yield函数都不会将这些值返回给调用者。 Instead a generator object is returned and the code itself is instead paused until you iterate over the generator. 而是返回一个生成器对象 ,并暂停代码本身,直到您遍历该生成器为止。 Each time a yield is encountered, the code is paused again: 每次遇到yield时,代码都会再次暂停:

>>> def pausing_generator():
...     print 'Top of the generator'
...     yield 1
...     print 'In the middle'
...     yield 2
...     print 'At the end'
... 
>>> gen = pausing_generator()
>>> gen
<generator object pausing_generator at 0x1081e0d70>
>>> next(gen)
Top of the generator
1
>>> next(gen)
In the middle
2
>>> next(gen)
At the end
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Calling the pausing_generator() function returned a generator object . 调用pausing_generator()函数将返回一个generator object Only iterating (using the next() function here) runs the actual code in the function, but pausing execution each time a yield is encountered. 仅迭代(在此使用next()函数)运行该函数中的实际代码,但是每次遇到yield都会暂停执行。

Your perms_generator function returns a such a generator object, and a recursive call would still return a generator object. 您的perms_generator函数将返回一个此类生成器对象,而递归调用仍将返回一个生成器对象。 You could yield the whole generator object, but then you are producing a generator that produces a generator, etc. until you come to the inner-most generator. 您可以yield整个生成器对象,但是随后您将生成一个生成器,该生成器将生成一个生成器,依此类推,直到到达最内层的生成器为止。

You can visualise this with print statements: 您可以使用print语句将其可视化:

>>> def countdown(i):
...     if not i:
...         return
...     yield i
...     recursive_result = countdown(i - 1)
...     print i, recursive_result
...     for recursive_elem in recursive_result:
...         yield recursive_elem
... 
>>> for i in countdown(5):
...     print i
... 
5
5 <generator object countdown at 0x1081e0e10>
4
4 <generator object countdown at 0x1081e0e60>
3
3 <generator object countdown at 0x1081e0eb0>
2
2 <generator object countdown at 0x1081e0f00>
1
1 <generator object countdown at 0x1081e0f50>

Here, the recursive calls returned a new generator object; 在这里,递归调用返回了一个新的生成器对象; if you wanted to have the elements produced by the generator your only choice is to loop over it and hand the elements down, not the generator object itself. 如果您希望由生成器生成元素 ,则唯一的选择是将其循环并向下传递元素,而不是生成器对象本身。

In Python 3, you can use yield from to delegate to a nested generator, including a recursive call: 在Python 3中,您可以使用yield from 委托给嵌套的生成器,包括递归调用:

def perms_generator(to_go, so_far=''):
    if not to_go:
        yield so_far
    else:
        for i, elem in enumerate(to_go):
            yield from perms_generator(to_go[:i] + to_go[i+1:], so_far + elem)

When encountering a yield from iteration continues into the recursive call instead of yielding the whole generator object. 当遇到迭代yield from继续执行递归调用,而不是产生整个生成器对象。

The difference between return and yield is that the former just returns a value. returnyield之间的区别在于,前者只返回一个值。 The latter means "wrap the value in a generator and then return the generator." 后者的意思是“将值包装在生成器中,然后返回生成器”。

So in all cases, the function perms_generator() returns a generator. 因此,在所有情况下,函数perms_generator()返回一个生成器。

The expression yield perms_generator() again would wrap the result of perms_generator() in a generator, giving you a generator of a generator. 表达yield perms_generator()再次将包的结果perms_generator()中的发电机,给你一个发电机的发电机。 That would mean the function would return different things; 那意味着函数将返回不同的东西; sometimes, it would be a simple generator and sometimes nested generators. 有时,它将是一个简单的生成器,有时是嵌套的生成器。 That would be very confusing for the consumer of your code. 对于您的代码使用者,这将非常令人困惑。

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

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