Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Licence CC BY-NC-ND Thierry Parmentelat & Arnaud Legout Inria - UCA

Dans ce notebook nous allons voir comment fabriquer une fonction génératrice qui appelle elle-même une autre fonction génératrice.

Complément - niveau avancé

Une fonction génératrice

Commençons à nous définir une fonction génératrice ; par exemple ici nous listons les diviseurs d’un entier, en excluant 1 et l’entier lui-même :

def divs(n, verbose=False):
    for i in range(2, n):
        if n % i == 0:
            if verbose: 
                print(f'trouvé diviseur {i} de {n}')
            yield i

Comme attendu, l’appel direct à cette fonction ne donne rien d’utile :

divs(28)
<generator object divs at 0x7f5768392d40>

Mais lorsqu’on l’utilise dans une boucle for:

for d in divs(28):
    print(d)
The history saving thread hit an unexpected error (OperationalError('attempt to write a readonly database')).History will not be written to the database.
2
4
7
14

Une fonction génératrice qui appelle une autre fonction génératrice

Bien, jusqu’ici c’est clair. Maintenant supposons que je veuille écrire une fonction génératrice qui énumère tous les diviseurs de tous les diviseurs d’un entier. Il s’agit donc, en sorte, d’écrire une fonction génératrice qui en appelle une autre - ici elle même.

Première idée

Première idée naïve pour faire cela, mais qui ne marche pas :

def divdivs(n):
    for i in divs(n):
        divs(i)
try:
    for i in divdivs(28):
        print(i)
except Exception as e:
    print(f"OOPS {e}")
OOPS 'NoneType' object is not iterable

Ce qui se passe ici, c’est que divdivs est perçue comme une fonction normale, lorsqu’on l’appelle elle ne retourne rien, donc None ; et c’est sur ce None qu’on essaie de faire la boucle for (à l’interieur du try), qui donc échoue.

Deuxième idée

Si on utilise juste yield, ça ne fait pas du tout ce qu’on veut :

def divdivs(n):
    for i in divs(n):
        yield divs(i)
try:
    for i in divdivs(28):
        print(i)
except Exception as e:
    print(f"OOPS {e}")
<generator object divs at 0x7f5768393880>
<generator object divs at 0x7f5768392a70>
<generator object divs at 0x7f5768393880>
<generator object divs at 0x7f5768392a70>

En effet, c’est logique, chaque yield dans divdivs() correspond à une itération de la boucle. Bref, il nous manque quelque chose dans le langage pour arriver à faire ce qu’on veut.

yield from

La construction du langage qui permet de faire ceci s’appelle yield from;

def divdivs(n):
    for i in divs(n):
        yield from divs(i, verbose=True)
try:
    for i in divdivs(28):
        print(i)
except Exception as e:
    print(f"OOPS {e}")
trouvé diviseur 2 de 4
2
trouvé diviseur 2 de 14
2
trouvé diviseur 7 de 14
7

Avec yield from, on peut indiquer que divdivs est une fonction génératrice, et qu’il faut évaluer divs(..) comme un générateur à utiliser comme tel; une fonction génératrice attend des yield, on indique qu’elle doit aussi chercher dans la sous-fonction divs.

Tout ceci signifie, dit autrement, que l’appel

yield from divs(...)

est grosso-modo équivalent à

for truc in divs(...):
   yield truc