Рекурсия с использованием yield

есть ли способ смешать рекурсию и yield заявление? Например, генератор бесконечных чисел (с использованием рекурсии) будет чем-то вроде:

def infinity(start):
    yield start
    # recursion here ...

>>> it = infinity(1)
>>> next(it)
1
>>> next(it)
2

пробовал:

def infinity(start):
    yield start
    infinity(start + 1)

и

def infinity(start):
    yield start
    yield infinity(start + 1)

но никто из них не сделал того, что я хотел, первый остановился после того, как он уступил start и вторая уступила start, затем генератор, а затем остановился.

Примечание: пожалуйста, я знаю вы можете сделать это с помощью while-loop:

def infinity(start):
    while True:
        yield start
        start += 1

Я просто хочу знать, можно ли это сделать рекурсивно.

3 ответов


Да, вы можете сделать это:

def infinity(start):
    yield start
    for x in infinity(start + 1):
        yield x

это приведет к ошибке, как только будет достигнута максимальная глубина рекурсии.

начиная с Python 3.3, вы сможете использовать

def infinity(start):
    yield start
    yield from infinity(start + 1)

Если вы просто вызываете свою функцию генератора рекурсивно, не зацикливаясь на ней или yield from - ing это, все, что вы делаете, это построить новый генератор, фактически не запуская тело функции или ничего не давая.

посмотреть PEP 380 для дальнейшего подробности.


в некоторых случаях может быть предпочтительнее использовать стек вместо рекурсии для генераторов. Это должно быть возможно переписать рекурсивный метод, используя стек и цикл while.

вот пример рекурсивного метода, который использует обратный вызов и может быть переписан с использованием логики стека:

def traverse_tree(callback):
    # Get the root node from somewhere.
    root = get_root_node()
    def recurse(node):
        callback(node)
        for child in node.get('children', []):
            recurse(child)
    recurse(root)

выше метод обходит дерево узлов, где каждый узел имеет children массив, который может содержать дочерние узлы. По мере того как каждый узел встречается, обратный вызов выдается и текущий узел передается ему.

метод можно использовать таким образом, распечатывая некоторые свойства на каждом узле.

def callback(node):
    print(node['id'])
traverse_tree(callback)

вместо этого используйте стек и напишите метод обхода как генератор

# A stack-based alternative to the traverse_tree method above.
def iternodes():
    stack = [get_root_node()]
    while stack:
        node = stack.pop()
        yield node
        for child in reversed(node.get('children', [])):
            stack.append(child)

(обратите внимание, что если вы хотите тот же порядок обхода, что и изначально, вам нужно изменить порядок детей, потому что первый ребенок, добавленный в стек, будет последним.)

теперь вы можете получить такое же поведение, как traverse_tree выше, но с генератором:

for node in iternodes():
    print(node['id'])

это не универсальное решение, но для некоторых генераторов вы можете получить хороший результат, заменяющий обработку стека для рекурсии.


поэтому в основном вам просто нужно добавить цикл for В где вам нужно вызвать свою функцию рекурсивно. Это относится к Python 2.7.