web-dev-qa-db-fra.com

Récursivité en utilisant le rendement

Existe-t-il un moyen de mélanger la récursivité et l'instruction yield? Par exemple, un générateur de nombres infinis (utilisant la récursivité) serait quelque chose comme:

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

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

J'ai essayé:

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

et

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

Mais aucun d'eux n'a fait ce que je voulais, le premier s'est arrêté après qu'il a donné start et le second a donné start, puis le générateur et puis s'est arrêté.

REMARQUE: S'il vous plaît, je sais que vous pouvez le faire en utilisant une boucle while:

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

Je veux juste savoir si cela peut être fait de manière récursive.

57
juliomalegria

Oui, vous pouvez le faire:

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

Cela entraînera une erreur une fois que la profondeur de récursivité maximale sera atteinte.

À partir de Python 3.3, vous pourrez utiliser

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

Si vous appelez simplement votre fonction de générateur de manière récursive sans boucler dessus ou yield from- Dans ce cas, tout ce que vous faites est de construire un nouveau générateur, sans réellement exécuter le corps de la fonction ni produire quoi que ce soit.

Voir PEP 38 pour plus de détails.

119
Sven Marnach

Dans certains cas, il peut être préférable d'utiliser une pile au lieu de la récursivité pour les générateurs. Il devrait être possible de réécrire une méthode récursive en utilisant une pile et une boucle while.

Voici un exemple de méthode récursive qui utilise un rappel et peut être réécrite à l'aide de la logique de pile:

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)

La méthode ci-dessus traverse une arborescence de nœuds où chaque nœud a un tableau children qui peut contenir des nœuds enfants. Lorsque chaque nœud est rencontré, le rappel est émis et le nœud actuel lui est transmis.

La méthode pourrait être utilisée de cette façon, en imprimant certaines propriétés sur chaque nœud.

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

tilisez plutôt une pile et écrivez la méthode de traversée comme générateur

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

(Notez que si vous voulez le même ordre de traversée qu'à l'origine, vous devez inverser l'ordre des enfants car le premier enfant ajouté à la pile sera le dernier à apparaître.)

Vous pouvez maintenant obtenir le même comportement que traverse_tree ci-dessus, mais avec un générateur:

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

Ce n'est pas une solution universelle, mais pour certains générateurs, vous pourriez obtenir un bon résultat en substituant le traitement de la pile à la récursivité.

11
t.888
def lprint(a):
    if isinstance(a, list):
        for i in a:
            yield from lprint(i)
    else:
        yield a

a = [[1, [2, 3], 4], [5, 6, [7, 8, [9]]]]
for i in lprint(b):
    print(i)
0