À quoi peuvent servir les fonctions du générateur Python?
je commence à apprendre Python et je suis tombé sur des fonctions de générateur, ceux qui ont une déclaration de rendement en eux. Je veux savoir quels types de problèmes ces fonctions sont vraiment bonnes à résoudre.
16 réponses
vous donnent une évaluation paresseuse. Vous les utilisez en itérant au-dessus d'eux, soit explicitement avec 'pour' ou implicitement en le passant à n'importe quelle fonction ou construction qui itère. Vous pouvez penser que les générateurs renvoient plusieurs articles, comme s'ils retournaient une liste, mais au lieu de les renvoyer tous en même temps, ils les renvoient un par un, et la fonction de générateur est interrompue jusqu'à ce que l'article suivant soit demandé.
générateurs sont bons pour le calcul de grands ensembles de résultats (en particulier les calculs impliquant des boucles eux-mêmes) où vous ne savez pas si vous allez avoir besoin de tous les résultats, ou où vous ne voulez pas allouer 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 il est plus pratique si cela s'est produit aussi tard que possible.
une autre utilisation pour les générateurs (qui est vraiment la même) est de remplacer les callbacks par itération. Dans certaines situations, vous voulez une fonction pour faire beaucoup de travail et parfois de rapport à l'appelant. Traditionnellement, vous utiliseriez une fonction de rappel pour ça. Vous passez cet appel à la fonction work-function et il appellera périodiquement cet appel. L'approche du générateur est que la fonction de travail (maintenant un générateur) ne sait rien au sujet du rappel, et cède simplement chaque fois qu'elle veut signaler quelque chose. L'appelant, au lieu d'écrire un rappel distinct et de le transmettre au work-function, fait tout le travail de rapport dans une petite boucle "pour" autour du générateur.
par exemple, dites que vous avez écrit un programme 'Recherche système de fichiers'. Vous pouvez effectuer la recherche dans son intégralité, recueillir les résultats et ensuite les afficher un à la fois. Tous les résultats doivent être recueillies avant vous a montré 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 trouver, ce qui serait plus de mémoire efficace et beaucoup plus conviviale pour l'utilisateur. Ce dernier pourrait être fait en passant la fonction d'impression des résultats à la fonction de recherche du 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 voulez voir un exemple des deux dernières approches, voir os.chemin.walk () (l'ancienne fonction de walking du système de fichiers avec callback) et os.marche() (le nouveau système de fichiers de marche du générateur. Bien entendu, si tu le voulais vraiment recueillir tous les résultats dans une liste, l'approche du générateur est triviale pour convertir à l'approche de la grande liste:
big_list = list(the_generator)
l'Une des raisons d'utiliser le générateur est de rendre la solution plus claire pour certains type de solutions.
l'autre est de traiter les résultats un à la fois, en évitant de construire d'énormes listes de résultats que vous traiteriez séparément de toute façon.
si vous avez une fonction fibonacci-up-to-n comme ceci:
# function version
def 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 version
def fibon(n):
a = b = 1
for 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 generator, la liste complète des 1000000 articles ne sera pas créée du tout, juste une valeur à la fois. Ce ne serait pas le cas lorsqu'on utilise la version list, où une liste serait créée en premier.
voir la section" Motivation "dans PEP 255 .
une utilisation non évidente des générateurs est la création de fonctions interruptibles, qui vous permet de faire des choses comme update UI ou exécuter plusieurs travaux" simultanément " (interleaved, en fait) tout en n'utilisant pas de threads.
je trouve cette explication qui efface mon doute. Parce qu'il y a une possibilité que la personne qui ne sait pas Generators
aussi ne sait pas yield
Retour
la déclaration de retour 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 plus tard, la fonction obtiendra un nouvel ensemble de variables.
"1519140920 le" Rendement
mais que faire si les variables locales ne sont pas jetées lorsque nous sortons d'une fonction? Cela implique que nous pouvons resume the function
où nous nous sommes quittés. C'est là que le concept de generators
est introduit et que l'énoncé yield
reprend là où le function
a été abandonné.
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()
0
In [4]: gen.next()
1
In [5]: gen.next()
donc c'est la différence entre return
et yield
déclarations en Python.
L'instruction de rendement est ce qui fait qu'une fonction est une fonction de générateur.
ainsi les générateurs sont un outil simple et puissant pour créer des itérateurs. Ils sont écrits comme des fonctions régulières, mais ils utilisent la déclaration yield
chaque fois qu'ils veulent retourner des données. Chaque fois que next() est appelé, le générateur reprend là où il s'est arrêté (il se souvient de toutes les valeurs de données et quelle instruction était dernière exécution).
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, alors un générateur pourrait aider:
def bufferedFetch():
while True:
buffer = getBigChunkOfData()
# insert some code to break on 'end of data'
for i in buffer:
yield i
le ci-dessus vous permet de séparer facilement le tamponnage du traitement. La fonction de consommation peut maintenant obtenir les valeurs une à une sans se soucier de la mise en tampon.
Exemple Du Monde Réel
disons que vous avez 100 millions de domaines dans votre table MySQL, et que vous souhaitez mettre à jour le rang Alexa pour chaque domaine.
la première chose dont vous avez besoin est de sélectionner vos noms de domaines dans la base de données.
disons que votre nom de table est domains
et le nom de colonne est domain
.
si vous utilisez SELECT domain FROM domains
il va retourner 100 millions de lignes qui va à consommer beaucoup de mémoire. Donc votre serveur pourrait se planter.
donc vous avez décidé de lancer le programme par lots. Disons que notre taille de lot est de 1000.
dans notre premier lot, nous allons interroger les 1000 premières rangées, vérifier le rang Alexa pour chaque domaine et mettre à jour la rangée de la base de données.
dans notre deuxième lot, nous travaillerons sur les 1000 lignes suivantes. Dans notre troisième lot, il sera de 2001 à 3000 et ainsi de suite.
maintenant nous avons besoin d'un fonction génératrice qui génère nos lots.
voici notre fonction de générateur:
def ResultGenerator(cursor, batchsize=1000):
while True:
results = cursor.fetchmany(batchsize)
if not results:
break
for result in results:
yield result
comme vous pouvez le voir, notre fonction garde yield
dans les résultats. Si vous avez utilisé le mot-clé return
au lieu de yield
, alors toute la fonction serait terminée une fois qu'elle atteint le retour.
return - returns only once
yield - returns multiple times
Si une fonction utilise le mot-clé yield
, alors c'est un générateur.
Maintenant vous pouvez itérer comme ceci:
db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")
for result in ResultGenerator(cursor):
doSomethingWith(result)
db.close()
j'ai trouvé que les générateurs sont très utiles pour nettoyer votre code et en vous donnant une façon 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 basées sur son propre traitement interne et quand 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é à utiliser.
un exemple abstrait serait un générateur de nombre de Fibonacci qui ne vit pas dans une boucle et quand il est appelé de n'importe où retournera toujours le prochain nombre dans la séquence:
def fib():
first = 0
second = 1
yield first
yield second
while 1:
next = first + second
yield next
first = second
second = next
fibgen1 = fib()
fibgen2 = fib()
Maintenant vous avez deux objets de générateur de nombre de Fibonacci que vous pouvez appeler de n'importe où dans votre code et ils retourneront toujours plus grands nombres de Fibonacci dans l'ordre suivant:
>>> fibgen1.next(); fibgen1.next(); fibgen1.next(); fibgen1.next()
0
1
1
2
>>> fibgen2.next(); fibgen2.next()
0
1
>>> fibgen1.next(); fibgen1.next()
3
5
la belle chose au sujet des générateurs est qu'ils encapsulent état sans avoir à passer à travers les cerceaux de la création d'objets. Une façon de les considérer est de les considérer comme des "fonctions" qui se souviennent de leur état interne.
j'ai eu L'exemple de Fibonacci de générateurs Python - que sont-ils? et avec un peu d'imagination, vous pouvez trouver beaucoup d'autres situations où les générateurs font une excellente alternative aux boucles for
et d'autres constructions itératives traditionnelles.
l'explication simple:
Considérez un for
déclaration
for item in iterable:
do_stuff()
la plupart du temps, tous les éléments de iterable
n'ont pas besoin d'être là dès le début, mais peuvent être générés à la volée car ils sont nécessaires. Cela peut être beaucoup plus efficace dans les deux
- espace (vous n'avez jamais besoin de stocker tous les articles simultanément) et
- le temps (l'itération peut se terminer avant que tous les éléments sont nécessaire.)
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 donnant des commandes:
def user_input():
while True:
wait_for_command()
cmd = get_command()
yield cmd
avec des générateurs, vous pouvez également avoir itération sur des séquences infinies, ce qui est bien sûr impossible lors de l'itération sur des conteneurs.
mes utilisations préférées sont les opérations de" filtrage "et de" réduction".
disons que nous lisons un fichier, et ne voulons que les lignes qui commencent par"##".
def filter2sharps( aSequence ):
for l in aSequence:
if l.startswith("##"):
yield l
nous pouvons alors utiliser la fonction de 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 lignes <Location>...</Location>
. [Pas de balises HTML, mais des lignes qui ressemblent à des balises.]
def reduceLocation( aSequence ):
keep= False
block= None
for line in aSequence:
if line.startswith("</Location"):
block.append( line )
yield block
block= None
keep= False
elif line.startsWith("<Location"):
block= [ line ]
keep= True
elif keep:
block.append( line )
else:
pass
if block is not None:
yield block # A partial block, icky
encore une fois, nous pouvons utiliser ce générateur dans un bon pour boucle.
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.
un exemple pratique où vous pourriez faire usage d'un générateur est si vous avez une sorte de forme et vous voulez itérer au-dessus de ses coins, bords ou quoi que ce soit. Pour mon propre projet (code source ici ) j'avais un rectangle:
class Rect():
def __init__(self, x, y, width, height):
self.l_top = (x, y)
self.r_top = (x+width, y)
self.r_bot = (x+width, y+height)
self.l_bot = (x, y+height)
def __iter__(self):
yield self.l_top
yield self.r_top
yield self.r_bot
yield self.l_bot
maintenant je peux créer un rectangle et boucle sur ses coins:
myrect=Rect(50, 50, 100, 100)
for corner in myrect:
print(corner)
au Lieu de __iter__
vous pourriez avoir une méthode iter_corners
et l'appeler avec for corner in myrect.iter_corners()
. C'est juste plus élégant d'utiliser __iter__
depuis lors, nous pouvons utiliser le nom de l'instance de classe directement dans l'expression for
.
quelques bonnes réponses ici, cependant, je recommande également une lecture complète du "tutoriel de programmation fonctionnelle de Python qui aide à expliquer certains des cas d'utilisation les plus puissants de générateurs.
- particulièrement intéressant est qu'il est maintenant possible de mettre à jour la variable de rendement de l'extérieur de la fonction de générateur , ce qui permet de créer des coroutines dynamiques et entrelacées avec relativement peu de effort.
- voir Aussi PEP 342: Coroutines via une meilleure Générateurs pour plus d'informations.
j'utilise des générateurs lorsque notre serveur web agit comme un proxy:
- le client demande une url mandatée du serveur
- Le serveur commence à charger l'url cible
- Le serveur rendements à retourner les résultats au client dès qu'il obtient
Depuis la méthode d'envoi d'un générateur n'a pas été mentionné, voici un exemple:
def test():
for i in xrange(5):
val = yield
print(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 qui fonctionne. Un cours plus avancé sur les générateurs dans la vidéo ci-dessous (y compris yield
de explination, générateurs pour le traitement parallèle, échappant à la limite de récursion, etc.)
tas de trucs. Tout le temps que vous voulez pour générer une séquence d'éléments, mais ne voulez pas avoir à "matérialiser" 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)
yield 2
for i in itertools.count(1):
candidate = i * 2 + 1
if not all(candidate % prime for prime in primes_found):
primes_found.add(candidate)
yield candidate
vous pourriez alors utiliser cela 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 insignifiants, mais vous pouvez voir comment il peut être utile pour le traitement de grande (potentiellement infinie!) les ensembles de données sans les générer à l'avance, ce qui n'est qu'une des utilisations les plus évidentes.
aussi bon pour imprimer les 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:
break
else:
yield(num)
for prime_num in genprime(100):
print(prime_num)