web-dev-qa-db-fra.com

Quand n'est-ce pas un bon moment pour utiliser python générateurs?

C'est plutôt l'inverse de Que pouvez-vous utiliser Python fonctions de générateur pour? : python Générateurs, expressions génératrices et itertools module est certaines de mes fonctionnalités préférées de python ces jours-ci. Ils sont particulièrement utiles lors de la configuration des chaînes d'opérations à effectuer sur un gros tas de données - j'utilise souvent eux lors du traitement des fichiers DSV.

alors quand est-ce non Un bon moment pour utiliser un générateur ou une expression génératrice, ou une fonction itertools?

  • Quand devrais-je préférer Zip() sur itertools.izip(), ou
  • range() Over xrange(), ou
  • [x for x in foo] plus de (x for x in foo)?

De toute évidence, nous devons éventuellement "résoudre" un générateur en données réelles, généralement en créant une liste ou en itération dessus avec une boucle de non-générateur. Parfois, nous avons juste besoin de connaître la longueur. Ce n'est pas ce que je demande.

Nous utilisons des générateurs afin que nous n'abandonnons pas de nouvelles listes en mémoire pour les données intermédiaires. Cela a surtout logique pour les grands ensembles de données. Est-ce que cela a du sens pour les petits ensembles de données aussi? Y a-t-il un compromis mémoire/processeur notable?

Je suis particulièrement intéressé si quelqu'un a fait un profilage à ce sujet, à la lumière de la discussion d'ouverture des yeux de liste de compréhension de la liste vs. Map () et filtre () . ( link alt )

81
David Eyk

tilisez une liste au lieu d'un générateur lorsque :

1) Vous devez accéder aux données multiple fois (c'est-à-dire cache les résultats au lieu de les recharger):

for i in outer:           # used once, okay to be a generator or return a list
    for j in inner:       # used multiple times, reusing a list is better
         ...

2) Vous avez besoin accès aléatoire (ou tout accès autre que l'ordre séquentiel en avant):

for i in reversed(data): ...     # generators aren't reversible

s[i], s[j] = s[j], s[i]          # generators aren't indexable

3) Vous devez -joindre strings (qui nécessite deux passes sur les données):

s = ''.join(data)                # lists are faster than generators in this use case

4) Vous utilisez pyypy qui ne peut parfois pas optimiser le code de générateur autant que possible avec des appels de fonction normaux et des manipulations de liste.

54
Raymond Hettinger

En général, n'utilisez pas de générateur lorsque vous avez besoin d'opérations de liste, comme Len (), inversé (), etc.

Il peut également y avoir des moments où vous n'aurez pas besoin d'une évaluation paresseuse (par exemple de faire tout le calcul à l'avance afin que vous puissiez libérer une ressource). Dans ce cas, une expression de liste pourrait être meilleure.

40
Ryan Ginstrom

Vous ne devriez jamais favoriser Zip Over izip , range sur xrange ou la liste des compréhensions sur compréhensions de générateur. In Python 3.0 range a xrange- semblable à la sémantique et Zip a izip- Semblante.

Les compréhensions de la liste sont en réalité plus claires comme list(frob(x) for x in foo) Pour ces fois, vous avez besoin d'une liste réelle.

17
Steven Huwig

Comme vous le mentionnez, "cela a surtout un sens pour les grands ensembles de données", je pense que cela répond à votre question.

Si vous n'allez pas frapper des murs, la performance-sage, vous pouvez toujours coller à des listes et aux fonctions standard. Ensuite, lorsque vous rencontrez des problèmes de performance, faites le commutateur.

Comme mentionné par @ u0b34a0f8ae dans les commentaires, toutefois, l'utilisation de générateurs au démarrage peut vous faciliter l'évolution des jeux de données plus importants.

7
monkut

En ce qui concerne les performances: Si vous utilisez PSYCO, les listes peuvent être assez plus rapides que les générateurs. Dans l'exemple ci-dessous, les listes sont presque 50% plus rapides lors de l'utilisation de psyco.ful ()

import psyco
import time
import cStringIO

def time_func(func):
    """The amount of time it requires func to run"""
    start = time.clock()
    func()
    return time.clock() - start

def fizzbuzz(num):
    """That algorithm we all know and love"""
    if not num % 3 and not num % 5:
        return "%d fizz buzz" % num
    Elif not num % 3:
        return "%d fizz" % num
    Elif not num % 5:
        return "%d buzz" % num
    return None

def with_list(num):
    """Try getting fizzbuzz with a list comprehension and range"""
    out = cStringIO.StringIO()
    for fibby in [fizzbuzz(x) for x in range(1, num) if fizzbuzz(x)]:
        print >> out, fibby
    return out.getvalue()

def with_genx(num):
    """Try getting fizzbuzz with generator expression and xrange"""
    out = cStringIO.StringIO()
    for fibby in (fizzbuzz(x) for x in xrange(1, num) if fizzbuzz(x)):
        print >> out, fibby
    return out.getvalue()

def main():
    """
    Test speed of generator expressions versus list comprehensions,
    with and without psyco.
    """

    #our variables
    nums = [10000, 100000]
    funcs = [with_list, with_genx]

    #  try without psyco 1st
    print "without psyco"
    for num in nums:
        print "  number:", num
        for func in funcs:
            print func.__name__, time_func(lambda : func(num)), "seconds"
        print

    #  now with psyco
    print "with psyco"
    psyco.full()
    for num in nums:
        print "  number:", num
        for func in funcs:
            print func.__name__, time_func(lambda : func(num)), "seconds"
        print

if __name__ == "__main__":
    main()

Résultats:

without psyco
  number: 10000
with_list 0.0519102208309 seconds
with_genx 0.0535933367509 seconds

  number: 100000
with_list 0.542204280744 seconds
with_genx 0.557837353115 seconds

with psyco
  number: 10000
with_list 0.0286369007033 seconds
with_genx 0.0513424889137 seconds

  number: 100000
with_list 0.335414877839 seconds
with_genx 0.580363490491 seconds
6
Ryan Ginstrom

Vous devez préférer les compréhensions de la liste si vous devez garder les valeurs autour de quelque chose d'autre plus tard et la taille de votre ensemble n'est pas trop grande.

Par exemple: vous créez une liste que vous allez boucler plusieurs fois plus tard dans votre programme.

Dans une certaine mesure, vous pouvez penser aux générateurs comme remplacement des compréhensions d'itération (LOOP) contre la liste comme type d'initialisation de la structure de données. Si vous souhaitez conserver la structure de données, utilisez des compréhensions de liste.

3
minty

En ce qui concerne la performance, je ne peux pas penser à des moments où vous voudriez utiliser une liste sur un générateur.

3
Jason Baker

Je n'ai jamais trouvé de situation où les générateurs entravaient ce que vous essayez de faire. Cependant, il y a beaucoup d'instances où l'utilisation de générateurs ne vous aiderait pas plus que de ne pas les utiliser.

Par exemple:

sorted(xrange(5))

N'offre aucune amélioration sur:

sorted(range(5))
3
Jeremy Cantrell