繁体   English   中英

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

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

测试一个人对递归的理解的一个很好的练习是编写一个函数,该函数生成字符串的所有排列:

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 

这段代码很好,但是从生成器的角度来看,我们可以通过使用生成器来显着提高效率:

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

(注意:最后一个for循环也可以用python 3.3中的yield from代替)

我的问题:为什么我们需要迭代每个递归调用的结果? 我知道yield返回一个生成器,但是从yield so_far语句yield so_far ,好像我们正在获取字符串,而不是需要迭代的内容。 相反,似乎我们可以取代

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

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

谢谢。 如果标题不清楚,请告诉我。 我感觉这个问题的内容与此SO问题有关

请记住,任何使用yield函数都不会将这些值返回给调用者。 而是返回一个生成器对象 ,并暂停代码本身,直到您遍历该生成器为止。 每次遇到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

调用pausing_generator()函数将返回一个generator object 仅迭代(在此使用next()函数)运行该函数中的实际代码,但是每次遇到yield都会暂停执行。

您的perms_generator函数将返回一个此类生成器对象,而递归调用仍将返回一个生成器对象。 您可以yield整个生成器对象,但是随后您将生成一个生成器,该生成器将生成一个生成器,依此类推,直到到达最内层的生成器为止。

您可以使用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>

在这里,递归调用返回了一个新的生成器对象; 如果您希望由生成器生成元素 ,则唯一的选择是将其循环并向下传递元素,而不是生成器对象本身。

在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)

当遇到迭代yield from继续执行递归调用,而不是产生整个生成器对象。

returnyield之间的区别在于,前者只返回一个值。 后者的意思是“将值包装在生成器中,然后返回生成器”。

因此,在所有情况下,函数perms_generator()返回一个生成器。

表达yield perms_generator()再次将包的结果perms_generator()中的发电机,给你一个发电机的发电机。 那意味着函数将返回不同的东西; 有时,它将是一个简单的生成器,有时是嵌套的生成器。 对于您的代码使用者,这将非常令人困惑。

暂无
暂无

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

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