À 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.

191
demandé sur Uli Köhler 2008-09-19 18:58:49

16 réponses

Les générateurs

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)
222
répondu Thomas Wouters 2011-06-08 22:00:24

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.

85
répondu nosklo 2008-09-19 15:09:28

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.

37
répondu Nickolay 2008-09-19 15:07:13

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).

33
répondu Mirage 2018-05-20 12:03: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, 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.

26
répondu Rafał Dowgird 2008-09-19 15:14:10

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()
26
répondu Giri 2018-05-20 11:16:57

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.

20
répondu Andz 2018-05-20 11:58:31

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.

18
répondu dF. 2008-09-19 15:15:03

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.

12
répondu S.Lott 2008-09-19 15:13:16

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 .

8
répondu Pithikos 2014-09-27 23:13:38

essentiellement éviter les fonctions de rappel lors de l'itération sur le maintien de l'état d'entrée.

voir ici et ici pour un aperçu de ce qui peut être fait en utilisant des générateurs.

6
répondu MvdD 2008-09-19 15:09:26

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.

4
répondu shongololo 2018-05-20 11:54:11

j'utilise des générateurs lorsque notre serveur web agit comme un proxy:

  1. le client demande une url mandatée du serveur
  2. Le serveur commence à charger l'url cible
  3. Le serveur rendements à retourner les résultats au client dès qu'il obtient
2
répondu Brian 2008-09-19 15:17:51

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.)

David Beazley sur les générateurs à PyCon 2014

2
répondu John Damen 2018-05-20 12:06:32

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.

1
répondu Nick Johnson 2008-09-19 18:03:49

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)
0
répondu tryptofan 2017-09-22 14:06:40