J'ai un objet générateur renvoyé par rendement multiple. La préparation pour appeler ce générateur est une opération plutôt longue. C'est pourquoi je souhaite réutiliser le générateur plusieurs fois.
y = FunctionWithYield()
for x in y: print(x)
#here must be something to reset 'y'
for x in y: print(x)
Bien sûr, je prends en compte la copie du contenu dans une liste simple. Existe-t-il un moyen de réinitialiser mon générateur?
y = list(y)
avec le reste de votre code inchangé.Les générateurs ne peuvent pas être rembobinés. Vous avez les options suivantes:
Exécutez à nouveau la fonction générateur en redémarrant la génération:
Stockez les résultats du générateur dans une structure de données sur la mémoire ou le disque que vous pouvez répéter:
L'inconvénient de l'option 1 est qu'elle calcule à nouveau les valeurs. Si cela demande beaucoup de ressources processeur, vous finissez par calculer deux fois. D'autre part, l'inconvénient de 2 est le stockage. La liste complète des valeurs sera stockée en mémoire. S'il y a trop de valeurs, cela peut ne pas être pratique.
Vous avez donc le compromis classique entre la mémoire et le traitement . Je ne peux pas imaginer un moyen de rembobiner le générateur sans stocker les valeurs ni les calculer à nouveau.
la source
la source
La solution la plus simple consiste probablement à envelopper la pièce coûteuse dans un objet et à la transmettre au générateur:
De cette façon, vous pouvez mettre en cache les calculs coûteux.
Si vous pouvez conserver tous les résultats dans la RAM en même temps, utilisez
list()
pour matérialiser les résultats du générateur dans une liste simple et travaillez avec cela.la source
Je souhaite proposer une solution différente à un ancien problème
L'avantage de cela par rapport à quelque chose comme
list(iterator)
c'est que c'estO(1)
la complexité de l'espace et l'list(iterator)
estO(n)
. L'inconvénient est que si vous avez uniquement accès à l'itérateur, mais pas à la fonction qui a produit l'itérateur, vous ne pouvez pas utiliser cette méthode. Par exemple, il peut sembler raisonnable de faire ce qui suit, mais cela ne fonctionnera pas.la source
Si la réponse de GrzegorzOledzki ne suffit pas, vous pourriez probablement l'utiliser
send()
pour atteindre votre objectif. Voir PEP-0342 pour plus de détails sur les générateurs améliorés et les expressions de rendement.MISE À JOUR: voir également
itertools.tee()
. Cela implique une partie de ce compromis entre la mémoire et le traitement mentionné ci-dessus, mais cela pourrait économiser de la mémoire en stockant simplement les résultats du générateur dans unlist
; cela dépend de la manière dont vous utilisez le générateur.la source
Si votre générateur est pur dans le sens où sa sortie ne dépend que des arguments passés et du numéro d'étape, et que vous voulez que le générateur résultant puisse être redémarré, voici un extrait de tri qui pourrait être utile:
les sorties:
la source
De la documentation officielle du tee :
Il est donc préférable de l'utiliser à la
list(iterable)
place dans votre cas.la source
list()
met tout l'itérable en mémoiretee()
si un itérateur consomme toutes les valeurs - c'est comme ça que çatee
marche.Utilisation d'une fonction wrapper pour gérer
StopIteration
Vous pouvez écrire une simple fonction wrapper dans votre fonction de génération de générateur qui suit lorsque le générateur est épuisé. Il le fera en utilisant l'
StopIteration
exception qu'un générateur lance lorsqu'il atteint la fin de l'itération.Comme vous pouvez le voir ci-dessus, lorsque notre fonction wrapper intercepte une
StopIteration
exception, elle réinitialise simplement l'objet générateur (en utilisant une autre instance de l'appel de fonction).Et puis, en supposant que vous définissiez votre fonction de fourniture de générateur quelque part comme ci-dessous, vous pouvez utiliser la syntaxe du décorateur de fonction Python pour l'envelopper implicitement:
la source
Vous pouvez définir une fonction qui renvoie votre générateur
Vous pouvez maintenant faire autant de fois que vous le souhaitez:
la source
Je ne sais pas ce que vous entendez par préparation coûteuse, mais je suppose que vous avez en fait
Si tel est le cas, pourquoi ne pas réutiliser
data
?la source
Il n'y a pas d'option pour réinitialiser les itérateurs. Iterator apparaît généralement lorsqu'il itère dans la
next()
fonction. Le seul moyen est de faire une sauvegarde avant d'itérer sur l'objet itérateur. Vérifiez ci-dessous.Création d'un objet itérateur avec les éléments 0 à 9
Itération à travers la fonction next () qui apparaîtra
Conversion de l'objet itérateur en liste
donc l'élément 0 est déjà sorti. De plus, tous les éléments sont affichés lorsque nous avons converti l'itérateur en liste.
Vous devez donc convertir l'itérateur en listes pour la sauvegarde avant de commencer l'itération. La liste peut être convertie en itérateur avec
iter(<list-object>)
la source
Vous pouvez maintenant utiliser
more_itertools.seekable
(un outil tiers) qui permet de réinitialiser les itérateurs.Installer via
> pip install more_itertools
Remarque: la consommation de mémoire augmente lors de l'avancement de l'itérateur, alors méfiez-vous des grands itérables.
la source
Vous pouvez le faire en utilisant itertools.cycle (), vous pouvez créer un itérateur avec cette méthode, puis exécuter une boucle for sur l'itérateur qui bouclera sur ses valeurs.
Par exemple:
générera 20 nombres, de 0 à 4 à plusieurs reprises.
Une note de la documentation:
la source
Ok, vous dites que vous voulez appeler un générateur plusieurs fois, mais l'initialisation coûte cher ... Et quelque chose comme ça?
Alternativement, vous pouvez simplement créer votre propre classe qui suit le protocole de l'itérateur et définit une sorte de fonction de «réinitialisation».
https://docs.python.org/2/library/stdtypes.html#iterator-types http://anandology.com/python-practice-book/iterators.html
la source
__call__
Ma réponse résout un problème légèrement différent: si le générateur est coûteux à initialiser et que chaque objet généré est coûteux à générer. Mais nous devons consommer le générateur plusieurs fois dans plusieurs fonctions. Afin d'appeler le générateur et chaque objet généré exactement une fois, nous pouvons utiliser des threads et exécuter chacune des méthodes consommatrices dans différents threads. Il se peut que nous n'atteignions pas le vrai parallélisme en raison du GIL, mais nous atteindrons notre objectif.
Cette approche a fait du bon travail dans le cas suivant: le modèle d'apprentissage en profondeur traite beaucoup d'images. Le résultat est beaucoup de masques pour un grand nombre d'objets sur l'image. Chaque masque consomme de la mémoire. Nous avons environ 10 méthodes qui font des statistiques et des métriques différentes, mais elles prennent toutes les images à la fois. Toutes les images ne peuvent pas tenir en mémoire. Les méthodes peuvent facilement être réécrites pour accepter l'itérateur.
Ussage:
la source
itertools.islice
ou pour asynchroneaiostream.stream.take
, et cet article vous permet de le faire de manière asynchrone / d'attente stackoverflow.com/a/42379188/149818Cela peut être fait par objet code. Voici l'exemple.
1 2 3 4
1 2 3 4
la source
exec
cela légèrement non recommandé pour un cas aussi simple.