Expressions génératrice par rapport à la compréhension de la liste

Quand devez-vous utiliser les expressions de générateur et quand devez-vous utiliser les compréhensions de liste en Python?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]
344
demandé sur Sнаđошƒаӽ 2008-09-07 00:07:59

8 réponses

Jean de réponse est bon (ce qui interprétations de la liste sont mieux lorsque vous souhaitez effectuer une itération sur quelque chose plusieurs fois). Cependant, il est également intéressant de noter que vous devriez utiliser une liste si vous voulez utiliser l'une des méthodes de liste. Par exemple, le code suivant ne fonctionnera pas:

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

fondamentalement, utilisez une expression de générateur si tout ce que vous faites est itératif une fois. Si vous souhaitez stocker et utiliser les résultats générés, alors vous êtes probablement mieux avec une liste compréhension.

puisque la performance est la raison la plus fréquente de choisir l'un par rapport à l'Autre, Mon conseil est de ne pas s'en inquiéter et d'en choisir un; si vous trouvez que votre programme tourne trop lentement, alors et seulement alors vous devriez revenir en arrière et vous soucier d'accorder votre code.

237
répondu Eli Courtwright 2008-09-06 20:54:08

Itération sur la générateur d'expression ou la compréhension de liste fera la même chose. Cependant ,le list comprehension va créer la liste entière en mémoire en premier tandis que le generator expression va créer les articles à la volée, de sorte que vous êtes en mesure de l'utiliser pour très grande (et aussi infinie!) séquence.

150
répondu dF. 2015-09-04 11:26:27

liste D'utilisation des interprétations lorsque le résultat doit être répété plusieurs fois, ou lorsque la vitesse est primordiale. Utilisez des expressions de générateur où la portée est grande ou infinie.

82
répondu John Millikin 2008-09-06 20:10:59

Le point important est que la compréhension de liste crée une nouvelle liste. Le générateur crée un objet itérable qui "filtre" le matériel source à la volée pendant que vous consommez les bits.

Imaginez que vous ayez un fichier journal de 2TB appelé "hugefile.txt", et vous voulez le contenu et la longueur de toutes les lignes qui commencent par le mot "entrée".

donc vous essayez de commencer par écrire une liste de compréhension:

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

cela dégrade tout le fichier, traite chaque ligne, et stocke les lignes correspondantes dans votre tableau. Ce tableau peut donc contenir jusqu'à 2TB de contenu. C'est beaucoup de RAM, et probablement pas pratique pour vos besoins.

donc nous pouvons utiliser un générateur pour appliquer un" filtre " à notre contenu. Aucune donnée n'est réellement lue jusqu'à ce que nous commencions à itérer le résultat.

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

pas une seule ligne n'a été lue dans notre dossier. En fait, disons que nous voulons filtrer notre résultat encore plus loin:

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

toujours rien n'a été lu, mais nous avons spécifié maintenant deux générateurs qui agiront sur nos données comme nous le souhaitons.

permet d'écrire nos lignes filtrées dans un autre fichier:

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

maintenant nous lisons le fichier d'entrée. Comme notre boucle for continue de demander des lignes supplémentaires, le générateur long_entries demande des lignes à partir de la entry_lines générateur, ne retournant que ceux dont la longueur est supérieure à 80 caractères. Et à son tour, le générateur entry_lines demande des lignes (filtrées comme indiqué) de l'itérateur logfile , qui lit à son tour le fichier.

donc au lieu de" pousser "des données à votre fonction de sortie sous la forme d'une liste entièrement remplie, vous donnez à la fonction de sortie un moyen de" tirer " des données seulement quand il est nécessaire. Dans notre cas, c'est beaucoup plus efficace, mais pas aussi flexible. Les générateurs sont à Sens Unique, à un seul passage; les données du fichier journal que nous avons lu sont immédiatement écartées, donc nous ne pouvons pas revenir à une ligne précédente. D'un autre côté, nous n'avons pas à nous soucier de conserver les données une fois que nous en aurons fini avec elles.

50
répondu tylerl 2014-04-04 09:14:57

l'avantage d'une expression génératrice est qu'elle utilise moins de mémoire puisqu'elle ne construit pas toute la liste à la fois. Générateur d'expressions sont utilisées au mieux lorsque la liste est un intermédiaire, comme en additionnant les résultats, ou la création d'un dict des résultats.

par exemple:

sum(x*2 for x in xrange(256))

dict( ((k, some_func(k) for k in some_list_of_keys) )

l'avantage est que la liste n'est pas complètement générée, et donc peu de mémoire est utilisée (et devrait aussi être plus rapide)

Vous devriez, cependant, utiliser des interprétations de liste lorsque le produit final désiré est une liste. Vous n'allez pas enregistrer de mémoire en utilisant les expressions generator, puisque vous voulez la liste générée. Vous obtenez également l'avantage d'être en mesure d'utiliser l'une des fonctions de liste comme trié ou inversé.

par exemple:

reversed( [x*2 for x in xrange(256)] )
42
répondu Chuck 2012-02-27 14:55:52

lors de la création d'un générateur à partir d'un objet mutable (comme une liste) soyez conscient que le générateur sera évalué sur l'état de la liste au moment de l'utilisation du générateur, et non au moment de la création du générateur:

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

S'il y a une chance que votre liste soit modifiée (ou un objet mutable à l'intérieur de cette liste) mais que vous ayez besoin de l'État à la création du générateur, vous devez utiliser une compréhension de liste à la place.

9
répondu freaker 2016-03-12 22:21:00

j'utilise le Hadoop Mincemeat module . Je pense que c'est un bon exemple pour prendre une note de:

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

ici, le générateur obtient des nombres d'un fichier texte (aussi grand que 15 Go) et applique des maths simples sur ces nombres en utilisant la Map-reduce de Hadoop. Si je n'avais pas utilisé la fonction rendement, mais plutôt une liste de compréhension, il aurait fallu beaucoup plus de temps pour calculer les sommes et la moyenne (sans parler de la complexité de l'espace).

Hadoop est un excellent exemple pour l'utilisation de tous les avantages des Générateurs.

5
répondu Murphy 2017-05-18 00:33:30

parfois, vous pouvez vous en tirer avec la fonction tee de itertools , elle renvoie plusieurs itérateurs pour le même générateur qui peuvent être utilisés indépendamment.

3
répondu Jacob Rigby 2015-09-04 11:27:07