Je sais comment fonctionne yield
. Je connais la permutation, pense-le simplement comme une simplicité mathématique.
Mais quelle est la vraie force de yield
? Quand devrais-je l'utiliser? Un exemple simple et bon, c'est mieux.
yield
est mieux utilisé lorsque vous avez une fonction qui retourne une séquence et que vous souhaitez itérer sur cette séquence, mais vous n'avez pas besoin d'avoir toutes les valeurs en mémoire à la fois.
Par exemple, j'ai un script python qui analyse une longue liste de fichiers CSV et je veux renvoyer chaque ligne à traiter dans une autre fonction. Je ne veux pas stocker les mégaoctets de données en mémoire en une seule fois, alors je yield
chaque ligne dans une structure de données python. Ainsi, la fonction permettant d’obtenir des lignes à partir du fichier pourrait ressembler à ceci:
def get_lines(files):
for f in files:
for line in f:
#preprocess line
yield line
Je peux ensuite utiliser la même syntaxe qu'avec les listes pour accéder au résultat de cette fonction:
for line in get_lines(files):
#process line
mais j'économise beaucoup de mémoire.
Pour le dire simplement, yield
vous donne un générateur. Vous l'utiliseriez là où vous utiliseriez normalement une return
dans une fonction. Comme un exemple vraiment artificiel coupé et collé à partir d'une invite ...
>>> def get_odd_numbers(i):
... return range(1, i, 2)
...
>>> def yield_odd_numbers(i):
... for x in range(1, i, 2):
... yield x
...
>>> foo = get_odd_numbers(10)
>>> bar = yield_odd_numbers(10)
>>> foo
[1, 3, 5, 7, 9]
>>> bar
<generator object yield_odd_numbers at 0x1029c6f50>
>>> bar.next()
1
>>> bar.next()
3
>>> bar.next()
5
Comme vous pouvez le constater, dans le premier cas, foo
conserve la liste complète en mémoire en une fois. Ce n'est pas un gros problème pour une liste à 5 éléments, mais si vous voulez une liste de 5 millions? Non seulement c'est un gros mangeur de mémoire, mais sa construction coûte beaucoup de temps au moment où la fonction est appelée. Dans le second cas, bar
vous donne simplement un générateur. Un générateur est un itératif - ce qui signifie que vous pouvez l'utiliser dans une boucle for, etc., mais chaque valeur n'est accessible qu'une seule fois. De plus, toutes les valeurs ne sont pas stockées en mémoire en même temps; l'objet générateur "se souvient" de l'endroit où il se trouvait dans la boucle la dernière fois que vous l'avez appelé - de cette façon, si vous utilisez une valeur itérable pour (par exemple) compter jusqu'à 50 milliards, vous n'avez pas à compter jusqu'à 50 milliards à la fois et stocker les 50 milliards de chiffres à compter. Encore une fois, c’est un bel exemple artificiel, vous utiliseriez probablement itertools
si vous vouliez vraiment compter jusqu’à 50 milliards. :)
C'est le cas d'utilisation le plus simple des générateurs. Comme vous l'avez dit, il peut être utilisé pour écrire des permutations efficaces, en utilisant yield
pour pousser des choses à travers la pile d'appels au lieu d'utiliser une sorte de variable de pile. Les générateurs peuvent également être utilisés pour la traversée d’arbres spécialisés, entre autres choses.
Lectures complémentaires:
Une autre utilisation est dans un client réseau. Utilisez 'yield' dans une fonction génératrice pour alterner tour à tour dans plusieurs sockets sans la complexité des threads.
Par exemple, un client de test matériel devait envoyer un plan R, G, B d’une image au micrologiciel. Les données devaient être envoyées en parallèle: rouge, vert, bleu, rouge, vert, bleu. Plutôt que de générer trois threads, j'avais un générateur qui lisait dans le fichier, encodait le tampon. Chaque tampon était un «rendement». Fin du fichier, la fonction est retournée et j'ai eu la fin de l'itération.
Mon code client a parcouru les trois fonctions du générateur, obtenant des tampons jusqu'à la fin de l'itération.
Je lis Structures de données et algorithmes en Python
Il existe une fonction fabonacci utilisant le rendement. Je pense que c'est le meilleur moment pour utiliser le rendement.
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a+b
vous pouvez utiliser ceci comme:
f = fibonacci()
for i, f in enumerate(f):
print i, f
if i >= 100: break
Donc, je pense que peut-être, lorsque le prochain élément dépend des éléments précédents, il est temps d'utiliser le rendement.