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 iComme 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