À quoi pouvez-vous utiliser les fonctions du générateur Python?
213
Je commence à apprendre Python et je suis tombé sur des fonctions de générateur, celles qui contiennent une déclaration de rendement. Je veux savoir quels types de problèmes ces fonctions sont vraiment bonnes à résoudre.
Les générateurs vous donnent une évaluation paresseuse. Vous les utilisez en les itérant, soit explicitement avec 'for', soit implicitement en les passant à n'importe quelle fonction ou construction qui itère. Vous pouvez considérer les générateurs comme renvoyant plusieurs éléments, comme s'ils renvoyaient une liste, mais au lieu de les renvoyer tous en même temps, ils les retournent un par un, et la fonction de générateur est suspendue jusqu'à ce que l'élément suivant soit demandé.
Les générateurs sont bons pour calculer de grands ensembles de résultats (en particulier les calculs impliquant des boucles elles-mêmes) lorsque vous ne savez pas si vous allez avoir besoin de tous les résultats, ou lorsque vous ne voulez pas allouer de la mémoire pour tous les résultats en même temps . Ou pour les situations où le générateur utilise un autre générateur, ou consomme une autre ressource, et c'est plus pratique si cela s'est produit le plus tard possible.
Une autre utilisation des générateurs (qui est vraiment la même) est de remplacer les rappels par l'itération. Dans certaines situations, vous souhaitez qu'une fonction effectue beaucoup de travail et fasse parfois rapport à l'appelant. Traditionnellement, vous utilisiez une fonction de rappel pour cela. Vous passez ce rappel à la fonction de travail et il appellera périodiquement ce rappel. L'approche du générateur est que la fonction de travail (maintenant un générateur) ne sait rien du rappel, et cède simplement chaque fois qu'elle veut signaler quelque chose. L'appelant, au lieu d'écrire un rappel séparé et de le transmettre à la fonction de travail, fait tout le travail de rapport dans une petite boucle "for" autour du générateur.
Par exemple, supposons que vous ayez écrit un programme de «recherche de système de fichiers». Vous pouvez effectuer la recherche dans son intégralité, collecter les résultats, puis les afficher un par un. Tous les résultats devraient être collectés avant que vous ne montriez le premier, et tous les résultats seraient en mémoire en même temps. Ou vous pouvez afficher les résultats pendant que vous les trouvez, ce qui serait plus efficace en mémoire et beaucoup plus convivial pour l'utilisateur. Ce dernier pourrait être fait en passant la fonction d'impression de résultat à la fonction de recherche de système de fichiers, ou cela pourrait être fait en faisant simplement de la fonction de recherche un générateur et en itérant sur le résultat.
Si vous souhaitez voir un exemple des deux dernières approches, consultez os.path.walk () (l'ancienne fonction de marche du système de fichiers avec rappel) et os.walk () (le nouveau générateur de marche du système de fichiers.) Bien sûr, si vous vouliez vraiment collecter tous les résultats dans une liste, l'approche du générateur est triviale à convertir à l'approche de la grande liste:
Un générateur tel que celui qui produit des listes de systèmes de fichiers effectue-t-il des actions en parallèle au code qui exécute ce générateur en boucle? Idéalement, l'ordinateur devrait exécuter le corps de la boucle (traiter le dernier résultat) tout en faisant ce que le générateur doit faire pour obtenir la valeur suivante.
Steven Lu
@StevenLu: À moins qu'il ne soit difficile de lancer manuellement les threads avant yieldet joinaprès pour obtenir le résultat suivant, il ne s'exécute pas en parallèle (et aucun générateur de bibliothèque standard ne le fait; le lancement secret de threads est mal vu). Le générateur s'arrête à chaque fois yieldjusqu'à ce que la valeur suivante soit demandée. Si le générateur encapsule les E / S, le système d'exploitation peut mettre en cache de manière proactive les données du fichier en supposant qu'il sera demandé sous peu, mais c'est le système d'exploitation, Python n'est pas impliqué.
ShadowRanger
90
L'une des raisons d'utiliser le générateur est de rendre la solution plus claire pour certains types de solutions.
L'autre consiste à traiter les résultats un par un, en évitant de créer d'énormes listes de résultats que vous traitez de toute façon.
Si vous avez une fonction fibonacci jusqu'à n comme celle-ci:
# function versiondef fibon(n):
a = b =1
result =[]for i in xrange(n):
result.append(a)
a, b = b, a + b
return result
Vous pouvez plus facilement écrire la fonction comme ceci:
# generator versiondef fibon(n):
a = b =1for i in xrange(n):yield a
a, b = b, a + b
La fonction est plus claire. Et si vous utilisez la fonction comme ceci:
for x in fibon(1000000):print x,
dans cet exemple, si vous utilisez la version du générateur, la liste complète de 1000000 éléments ne sera pas créée du tout, une seule valeur à la fois. Ce ne serait pas le cas lors de l'utilisation de la version liste, où une liste serait créée en premier.
Une utilisation non évidente des générateurs crée des fonctions interruptibles, qui vous permettent de faire des choses comme mettre à jour l'interface utilisateur ou exécuter plusieurs tâches "simultanément" (entrelacées, en fait) sans utiliser de threads.
La section Motivation est intéressante en ce qu'elle contient un exemple spécifique: "Lorsqu'une fonction de producteur a un travail suffisamment difficile pour nécessiter le maintien de l'état entre les valeurs produites, la plupart des langages de programmation n'offrent aucune solution agréable et efficace au-delà de l'ajout d'une fonction de rappel à l'argument du producteur. list ... Par exemple, tokenize.py dans la bibliothèque standard adopte cette approche "
Ben Creasy
38
Je trouve cette explication qui dissipe mon doute. Parce qu'il est possible qu'une personne qui ne sait pas Generatorsne connaisse pasyield
Revenir
L'instruction return est l'endroit où toutes les variables locales sont détruites et la valeur résultante est rendue (retournée) à l'appelant. Si la même fonction est appelée quelque temps plus tard, la fonction obtiendra un nouvel ensemble de variables.
rendement
Mais que se passe-t-il si les variables locales ne sont pas supprimées lorsque nous quittons une fonction? Cela implique que nous pouvons resume the functionoù nous nous sommes arrêtés. C'est là que le concept de generatorssont introduits et la yielddéclaration reprend là où elle functions'était arrêtée.
def generate_integers(N):for i in xrange(N):yield i
In[1]: gen = generate_integers(3)In[2]: gen
<generator object at 0x8117f90>In[3]: gen.next()0In[4]: gen.next()1In[5]: gen.next()
Voilà donc la différence entre returnetyield instructions en Python.
L'énoncé de rendement est ce qui fait d'une fonction une fonction de générateur.
Les générateurs sont donc un outil simple et puissant pour créer des itérateurs. Ils sont écrits comme des fonctions normales, mais ils utilisent l' yieldinstruction chaque fois qu'ils veulent renvoyer des données. Chaque fois que next () est appelé, le générateur reprend là où il s'était arrêté (il se souvient de toutes les valeurs de données et de la dernière instruction exécutée).
Disons que votre table MySQL contient 100 millions de domaines et que vous souhaitez mettre à jour le classement Alexa pour chaque domaine.
La première chose dont vous avez besoin est de sélectionner vos noms de domaine dans la base de données.
Disons que le nom de votre table est domainset le nom de la colonne est domain.
Si vous utilisez, SELECT domain FROM domainscela retournera 100 millions de lignes, ce qui consommera beaucoup de mémoire. Votre serveur pourrait donc se bloquer.
Vous avez donc décidé d'exécuter le programme par lots. Disons que notre taille de lot est de 1000.
Dans notre premier lot, nous interrogerons les 1000 premières lignes, vérifierons le classement Alexa pour chaque domaine et mettrons à jour la ligne de base de données.
Dans notre deuxième lot, nous travaillerons sur les 1000 lignes suivantes. Dans notre troisième lot, ce sera de 2001 à 3000 et ainsi de suite.
Maintenant, nous avons besoin d'une fonction de générateur qui génère nos lots.
Voici notre fonction de générateur:
defResultGenerator(cursor, batchsize=1000):whileTrue:
results = cursor.fetchmany(batchsize)ifnot results:breakfor result in results:yield result
Comme vous pouvez le voir, notre fonction conserve yieldles résultats. Si vous utilisiez le mot-clé à la returnplace de yield, alors la fonction entière serait terminée une fois qu'elle serait revenue.
return- returns only once
yield- returns multiple times
Si une fonction utilise le mot-clé yield c'est un générateur.
Vous pouvez maintenant répéter comme ceci:
db =MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")for result inResultGenerator(cursor):
doSomethingWith(result)
db.close()
ce serait plus pratique, si le rendement pouvait être expliqué en termes de programmation récursive / dyanmique!
igaurav
27
Mise en mémoire tampon. Lorsqu'il est efficace de récupérer des données en gros morceaux, mais de les traiter en petits morceaux, un générateur peut aider:
def bufferedFetch():whileTrue:
buffer = getBigChunkOfData()# insert some code to break on 'end of data'for i in buffer:yield i
Ce qui précède vous permet de séparer facilement la mise en mémoire tampon du traitement. La fonction consommateur peut maintenant simplement obtenir les valeurs une par une sans se soucier de la mise en mémoire tampon.
Si getBigChuckOfData n'est pas paresseux, je ne comprends pas quel est le rendement des avantages ici. Qu'est-ce qu'un cas d'utilisation pour cette fonction?
Sean Geoffrey Pietz
1
Mais le fait est que, IIUC, bufferedFetch est paresseux l'appel à getBigChunkOfData. Si getBigChunkOfData était déjà paresseux, alors bufferedFetch serait inutile. Chaque appel à bufferedFetch () retournera un élément tampon, même si un BigChunk a déjà été lu. Et vous n'avez pas besoin de compter explicitement le nombre de l'élément suivant à retourner, car la mécanique du rendement le fait implicitement.
hmijail pleure les démissionnaires
21
J'ai trouvé que les générateurs sont très utiles pour nettoyer votre code et en vous donnant un moyen très unique d'encapsuler et de modulariser le code. Dans une situation où vous avez besoin de quelque chose pour cracher constamment des valeurs en fonction de son propre traitement interne et lorsque ce quelque chose doit être appelé de n'importe où dans votre code (et pas seulement dans une boucle ou un bloc par exemple), les générateurs sont la fonctionnalité pour utilisation.
Un exemple abstrait serait un générateur de nombres de Fibonacci qui ne vit pas dans une boucle et quand il est appelé de n'importe où, il retournera toujours le numéro suivant dans la séquence:
def fib():
first =0
second =1yield first
yield second
while1:
next = first + second
yield next
first = second
second = next
fibgen1 = fib()
fibgen2 = fib()
Vous avez maintenant deux objets générateurs de nombres Fibonacci que vous pouvez appeler de n'importe où dans votre code et ils renverront toujours des nombres Fibonacci toujours plus grands dans l'ordre comme suit:
La belle chose à propos des générateurs est qu'ils encapsulent l'état sans avoir à passer par les cerceaux de la création d'objets. Une façon de les considérer est comme des "fonctions" qui se souviennent de leur état interne.
J'ai obtenu l'exemple de Fibonacci de Python Generators - Quels sont-ils? et avec un peu d'imagination, vous pouvez trouver beaucoup d'autres situations où les générateurs constituent une excellente alternative aux forboucles et autres constructions d'itérations traditionnelles.
L'explication simple: considérez une fordéclaration
for item in iterable:
do_stuff()
La plupart du temps, tous les éléments iterablene doivent pas nécessairement être présents dès le début, mais peuvent être générés à la volée selon les besoins. Cela peut être beaucoup plus efficace à la fois
l'espace (vous n'avez jamais besoin de stocker tous les articles simultanément) et
temps (l'itération peut se terminer avant que tous les éléments soient nécessaires).
D'autres fois, vous ne connaissez même pas tous les articles à l'avance. Par exemple:
for command in user_input():
do_stuff_with(command)
Vous n'avez aucun moyen de connaître toutes les commandes de l'utilisateur à l'avance, mais vous pouvez utiliser une belle boucle comme celle-ci si vous avez un générateur vous remettant des commandes:
Avec les générateurs, vous pouvez également avoir une itération sur des séquences infinies, ce qui n'est bien sûr pas possible lors de l'itération sur des conteneurs.
... et une séquence infinie pourrait être générée en parcourant à plusieurs reprises une petite liste, revenant au début une fois la fin atteinte. Je l'utilise pour sélectionner des couleurs dans les graphiques ou produire des lanceurs ou des filateurs occupés dans le texte.
Andrej Panjkov
@mataap: Il y en a un itertoolpour ça - voyez cycles.
martineau
12
Mes utilisations préférées sont les opérations de "filtrage" et de "réduction".
Disons que nous lisons un fichier et que nous voulons uniquement les lignes qui commencent par "##".
def filter2sharps( aSequence ):for l in aSequence:if l.startswith("##"):yield l
Nous pouvons ensuite utiliser la fonction générateur dans une boucle appropriée
source= file(...)for line in filter2sharps( source.readlines()):print line
source.close()
L'exemple de réduction est similaire. Disons que nous avons un fichier où nous devons localiser des blocs de <Location>...</Location>lignes. [Pas des balises HTML, mais des lignes qui ressemblent à des balises.]
def reduceLocation( aSequence ):
keep=False
block=Nonefor line in aSequence:if line.startswith("</Location"):
block.append( line )yield block
block=None
keep=Falseelif line.startsWith("<Location"):
block=[ line ]
keep=Trueelif keep:
block.append( line )else:passif block isnotNone:yield block # A partial block, icky
Encore une fois, nous pouvons utiliser ce générateur dans une boucle for appropriée.
source = file(...)for b in reduceLocation( source.readlines()):print b
source.close()
L'idée est qu'une fonction de générateur nous permet de filtrer ou de réduire une séquence, produisant une autre séquence une valeur à la fois.
fileobj.readlines()lirait l'intégralité du fichier dans une liste en mémoire, ce qui irait à l'encontre de l'utilisation des générateurs. Étant donné que les objets fichier sont déjà itérables, vous pouvez utiliser à la for b in your_generator(fileobject):place. De cette façon, votre fichier sera lu une ligne à la fois, pour éviter de lire tout le fichier.
nosklo le
ReduceLocation est assez bizarre, donnant une liste, pourquoi ne pas simplement donner chaque ligne? Le filtrage et la réduction sont également des commandes intégrées avec les comportements attendus (voir l'aide d'ipython, etc.), votre utilisation de "réduire" est identique à celle du filtre.
James Antill,
Bon point sur les readlines (). Je réalise généralement que les fichiers sont des itérateurs de ligne de première classe lors des tests unitaires.
S.Lott
En fait, la "réduction" combine plusieurs lignes individuelles en un objet composite. D'accord, c'est une liste, mais c'est toujours une réduction tirée de la source.
S.Lott
9
Un exemple pratique où vous pourriez utiliser un générateur est si vous avez une sorte de forme et que vous souhaitez parcourir ses coins, ses bords ou autre chose. Pour mon propre projet (code source ici ), j'avais un rectangle:
Maintenant, je peux créer un rectangle et une boucle sur ses coins:
myrect=Rect(50,50,100,100)for corner in myrect:print(corner)
Au lieu de cela, __iter__vous pourriez avoir une méthode iter_cornerset l'appeler avec for corner in myrect.iter_corners(). Il est juste plus élégant à utiliser __iter__car nous pouvons alors utiliser le nom d'instance de classe directement dans l' forexpression.
Quelques bonnes réponses ici, cependant, je recommanderais également une lecture complète du didacticiel de programmation fonctionnelle Python qui aide à expliquer certains des cas d'utilisation les plus puissants des générateurs.
Puisque la méthode d'envoi d'un générateur n'a pas été mentionnée, voici un exemple:
def test():for i in xrange(5):
val =yieldprint(val)
t = test()# Proceed to 'yield' statement
next(t)# Send value to yield
t.send(1)
t.send('2')
t.send([3])
Il montre la possibilité d'envoyer une valeur à un générateur en marche. Un cours plus avancé sur les générateurs dans la vidéo ci-dessous (y compris yieldd'explication, générateurs pour le traitement parallèle, échapper à la limite de récursivité, etc.)
Des tas de trucs. Chaque fois que vous souhaitez générer une séquence d'éléments, mais ne voulez pas avoir à les «matérialiser» tous dans une liste à la fois. Par exemple, vous pourriez avoir un générateur simple qui renvoie des nombres premiers:
def primes():
primes_found = set()
primes_found.add(2)yield2for i in itertools.count(1):
candidate = i *2+1ifnot all(candidate % prime for prime in primes_found):
primes_found.add(candidate)yield candidate
Vous pouvez ensuite l'utiliser pour générer les produits des nombres premiers suivants:
def prime_products():
primeiter = primes()
prev = primeiter.next()for prime in primeiter:yield prime * prev
prev = prime
Ce sont des exemples assez triviaux, mais vous pouvez voir comment cela peut être utile pour traiter de grands ensembles de données (potentiellement infinis!) Sans les générer à l'avance, ce qui n'est qu'une des utilisations les plus évidentes.
sinon aucun (candidat% prime pour prime dans primes_found) devrait être si tout (candidat% prime pour prime dans primes_found)
rjmunro
Oui, je voulais écrire "sinon aucun (candidat% prime == 0 pour prime dans primes_found). Le tien est cependant un peu plus net. :)
Nick Johnson
Je suppose que vous avez oublié de supprimer le «pas» de sinon tous (candidat% prime pour prime dans primes_found)
Thava
0
Convient également pour l'impression des nombres premiers jusqu'à n:
def genprime(n=10):for num in range(3, n+1):for factor in range(2, num):if num%factor ==0:breakelse:yield(num)for prime_num in genprime(100):print(prime_num)
Réponses:
Les générateurs vous donnent une évaluation paresseuse. Vous les utilisez en les itérant, soit explicitement avec 'for', soit implicitement en les passant à n'importe quelle fonction ou construction qui itère. Vous pouvez considérer les générateurs comme renvoyant plusieurs éléments, comme s'ils renvoyaient une liste, mais au lieu de les renvoyer tous en même temps, ils les retournent un par un, et la fonction de générateur est suspendue jusqu'à ce que l'élément suivant soit demandé.
Les générateurs sont bons pour calculer de grands ensembles de résultats (en particulier les calculs impliquant des boucles elles-mêmes) lorsque vous ne savez pas si vous allez avoir besoin de tous les résultats, ou lorsque vous ne voulez pas allouer de la mémoire pour tous les résultats en même temps . Ou pour les situations où le générateur utilise un autre générateur, ou consomme une autre ressource, et c'est plus pratique si cela s'est produit le plus tard possible.
Une autre utilisation des générateurs (qui est vraiment la même) est de remplacer les rappels par l'itération. Dans certaines situations, vous souhaitez qu'une fonction effectue beaucoup de travail et fasse parfois rapport à l'appelant. Traditionnellement, vous utilisiez une fonction de rappel pour cela. Vous passez ce rappel à la fonction de travail et il appellera périodiquement ce rappel. L'approche du générateur est que la fonction de travail (maintenant un générateur) ne sait rien du rappel, et cède simplement chaque fois qu'elle veut signaler quelque chose. L'appelant, au lieu d'écrire un rappel séparé et de le transmettre à la fonction de travail, fait tout le travail de rapport dans une petite boucle "for" autour du générateur.
Par exemple, supposons que vous ayez écrit un programme de «recherche de système de fichiers». Vous pouvez effectuer la recherche dans son intégralité, collecter les résultats, puis les afficher un par un. Tous les résultats devraient être collectés avant que vous ne montriez le premier, et tous les résultats seraient en mémoire en même temps. Ou vous pouvez afficher les résultats pendant que vous les trouvez, ce qui serait plus efficace en mémoire et beaucoup plus convivial pour l'utilisateur. Ce dernier pourrait être fait en passant la fonction d'impression de résultat à la fonction de recherche de système de fichiers, ou cela pourrait être fait en faisant simplement de la fonction de recherche un générateur et en itérant sur le résultat.
Si vous souhaitez voir un exemple des deux dernières approches, consultez os.path.walk () (l'ancienne fonction de marche du système de fichiers avec rappel) et os.walk () (le nouveau générateur de marche du système de fichiers.) Bien sûr, si vous vouliez vraiment collecter tous les résultats dans une liste, l'approche du générateur est triviale à convertir à l'approche de la grande liste:
la source
yield
etjoin
après pour obtenir le résultat suivant, il ne s'exécute pas en parallèle (et aucun générateur de bibliothèque standard ne le fait; le lancement secret de threads est mal vu). Le générateur s'arrête à chaque foisyield
jusqu'à ce que la valeur suivante soit demandée. Si le générateur encapsule les E / S, le système d'exploitation peut mettre en cache de manière proactive les données du fichier en supposant qu'il sera demandé sous peu, mais c'est le système d'exploitation, Python n'est pas impliqué.L'une des raisons d'utiliser le générateur est de rendre la solution plus claire pour certains types de solutions.
L'autre consiste à traiter les résultats un par un, en évitant de créer d'énormes listes de résultats que vous traitez de toute façon.
Si vous avez une fonction fibonacci jusqu'à n comme celle-ci:
Vous pouvez plus facilement écrire la fonction comme ceci:
La fonction est plus claire. Et si vous utilisez la fonction comme ceci:
dans cet exemple, si vous utilisez la version du générateur, la liste complète de 1000000 éléments ne sera pas créée du tout, une seule valeur à la fois. Ce ne serait pas le cas lors de l'utilisation de la version liste, où une liste serait créée en premier.
la source
list(fibon(5))
Voir la section "Motivation" dans PEP 255 .
Une utilisation non évidente des générateurs crée des fonctions interruptibles, qui vous permettent de faire des choses comme mettre à jour l'interface utilisateur ou exécuter plusieurs tâches "simultanément" (entrelacées, en fait) sans utiliser de threads.
la source
Je trouve cette explication qui dissipe mon doute. Parce qu'il est possible qu'une personne qui ne sait pas
Generators
ne connaisse pasyield
Revenir
L'instruction return est l'endroit où toutes les variables locales sont détruites et la valeur résultante est rendue (retournée) à l'appelant. Si la même fonction est appelée quelque temps plus tard, la fonction obtiendra un nouvel ensemble de variables.
rendement
Mais que se passe-t-il si les variables locales ne sont pas supprimées lorsque nous quittons une fonction? Cela implique que nous pouvons
resume the function
où nous nous sommes arrêtés. C'est là que le concept degenerators
sont introduits et layield
déclaration reprend là où ellefunction
s'était arrêtée.Voilà donc la différence entre
return
etyield
instructions en Python.L'énoncé de rendement est ce qui fait d'une fonction une fonction de générateur.
Les générateurs sont donc un outil simple et puissant pour créer des itérateurs. Ils sont écrits comme des fonctions normales, mais ils utilisent l'
yield
instruction chaque fois qu'ils veulent renvoyer des données. Chaque fois que next () est appelé, le générateur reprend là où il s'était arrêté (il se souvient de toutes les valeurs de données et de la dernière instruction exécutée).la source
Exemple du monde réel
Disons que votre table MySQL contient 100 millions de domaines et que vous souhaitez mettre à jour le classement Alexa pour chaque domaine.
La première chose dont vous avez besoin est de sélectionner vos noms de domaine dans la base de données.
Disons que le nom de votre table est
domains
et le nom de la colonne estdomain
.Si vous utilisez,
SELECT domain FROM domains
cela retournera 100 millions de lignes, ce qui consommera beaucoup de mémoire. Votre serveur pourrait donc se bloquer.Vous avez donc décidé d'exécuter le programme par lots. Disons que notre taille de lot est de 1000.
Dans notre premier lot, nous interrogerons les 1000 premières lignes, vérifierons le classement Alexa pour chaque domaine et mettrons à jour la ligne de base de données.
Dans notre deuxième lot, nous travaillerons sur les 1000 lignes suivantes. Dans notre troisième lot, ce sera de 2001 à 3000 et ainsi de suite.
Maintenant, nous avons besoin d'une fonction de générateur qui génère nos lots.
Voici notre fonction de générateur:
Comme vous pouvez le voir, notre fonction conserve
yield
les résultats. Si vous utilisiez le mot-clé à lareturn
place deyield
, alors la fonction entière serait terminée une fois qu'elle serait revenue.Si une fonction utilise le mot-clé
yield
c'est un générateur.Vous pouvez maintenant répéter comme ceci:
la source
Mise en mémoire tampon. Lorsqu'il est efficace de récupérer des données en gros morceaux, mais de les traiter en petits morceaux, un générateur peut aider:
Ce qui précède vous permet de séparer facilement la mise en mémoire tampon du traitement. La fonction consommateur peut maintenant simplement obtenir les valeurs une par une sans se soucier de la mise en mémoire tampon.
la source
J'ai trouvé que les générateurs sont très utiles pour nettoyer votre code et en vous donnant un moyen très unique d'encapsuler et de modulariser le code. Dans une situation où vous avez besoin de quelque chose pour cracher constamment des valeurs en fonction de son propre traitement interne et lorsque ce quelque chose doit être appelé de n'importe où dans votre code (et pas seulement dans une boucle ou un bloc par exemple), les générateurs sont la fonctionnalité pour utilisation.
Un exemple abstrait serait un générateur de nombres de Fibonacci qui ne vit pas dans une boucle et quand il est appelé de n'importe où, il retournera toujours le numéro suivant dans la séquence:
Vous avez maintenant deux objets générateurs de nombres Fibonacci que vous pouvez appeler de n'importe où dans votre code et ils renverront toujours des nombres Fibonacci toujours plus grands dans l'ordre comme suit:
La belle chose à propos des générateurs est qu'ils encapsulent l'état sans avoir à passer par les cerceaux de la création d'objets. Une façon de les considérer est comme des "fonctions" qui se souviennent de leur état interne.
J'ai obtenu l'exemple de Fibonacci de Python Generators - Quels sont-ils? et avec un peu d'imagination, vous pouvez trouver beaucoup d'autres situations où les générateurs constituent une excellente alternative aux
for
boucles et autres constructions d'itérations traditionnelles.la source
L'explication simple: considérez une
for
déclarationLa plupart du temps, tous les éléments
iterable
ne doivent pas nécessairement être présents dès le début, mais peuvent être générés à la volée selon les besoins. Cela peut être beaucoup plus efficace à la foisD'autres fois, vous ne connaissez même pas tous les articles à l'avance. Par exemple:
Vous n'avez aucun moyen de connaître toutes les commandes de l'utilisateur à l'avance, mais vous pouvez utiliser une belle boucle comme celle-ci si vous avez un générateur vous remettant des commandes:
Avec les générateurs, vous pouvez également avoir une itération sur des séquences infinies, ce qui n'est bien sûr pas possible lors de l'itération sur des conteneurs.
la source
itertool
pour ça - voyezcycles
.Mes utilisations préférées sont les opérations de "filtrage" et de "réduction".
Disons que nous lisons un fichier et que nous voulons uniquement les lignes qui commencent par "##".
Nous pouvons ensuite utiliser la fonction générateur dans une boucle appropriée
L'exemple de réduction est similaire. Disons que nous avons un fichier où nous devons localiser des blocs de
<Location>...</Location>
lignes. [Pas des balises HTML, mais des lignes qui ressemblent à des balises.]Encore une fois, nous pouvons utiliser ce générateur dans une boucle for appropriée.
L'idée est qu'une fonction de générateur nous permet de filtrer ou de réduire une séquence, produisant une autre séquence une valeur à la fois.
la source
fileobj.readlines()
lirait l'intégralité du fichier dans une liste en mémoire, ce qui irait à l'encontre de l'utilisation des générateurs. Étant donné que les objets fichier sont déjà itérables, vous pouvez utiliser à lafor b in your_generator(fileobject):
place. De cette façon, votre fichier sera lu une ligne à la fois, pour éviter de lire tout le fichier.Un exemple pratique où vous pourriez utiliser un générateur est si vous avez une sorte de forme et que vous souhaitez parcourir ses coins, ses bords ou autre chose. Pour mon propre projet (code source ici ), j'avais un rectangle:
Maintenant, je peux créer un rectangle et une boucle sur ses coins:
Au lieu de cela,
__iter__
vous pourriez avoir une méthodeiter_corners
et l'appeler avecfor corner in myrect.iter_corners()
. Il est juste plus élégant à utiliser__iter__
car nous pouvons alors utiliser le nom d'instance de classe directement dans l'for
expression.la source
Évitant essentiellement les fonctions de rappel lors de l'itération sur l'état de maintien d'entrée.
Voir ici et ici pour un aperçu de ce qui peut être fait en utilisant des générateurs.
la source
Quelques bonnes réponses ici, cependant, je recommanderais également une lecture complète du didacticiel de programmation fonctionnelle Python qui aide à expliquer certains des cas d'utilisation les plus puissants des générateurs.
la source
Puisque la méthode d'envoi d'un générateur n'a pas été mentionnée, voici un exemple:
Il montre la possibilité d'envoyer une valeur à un générateur en marche. Un cours plus avancé sur les générateurs dans la vidéo ci-dessous (y compris
yield
d'explication, générateurs pour le traitement parallèle, échapper à la limite de récursivité, etc.)David Beazley sur les générateurs à PyCon 2014
la source
J'utilise des générateurs lorsque notre serveur Web agit en tant que proxy:
la source
Des tas de trucs. Chaque fois que vous souhaitez générer une séquence d'éléments, mais ne voulez pas avoir à les «matérialiser» tous dans une liste à la fois. Par exemple, vous pourriez avoir un générateur simple qui renvoie des nombres premiers:
Vous pouvez ensuite l'utiliser pour générer les produits des nombres premiers suivants:
Ce sont des exemples assez triviaux, mais vous pouvez voir comment cela peut être utile pour traiter de grands ensembles de données (potentiellement infinis!) Sans les générer à l'avance, ce qui n'est qu'une des utilisations les plus évidentes.
la source
Convient également pour l'impression des nombres premiers jusqu'à n:
la source