web-dev-qa-db-fra.com

Compréhension de liste sans [] en Python

Rejoindre une liste:

>>> ''.join([ str(_) for _ in xrange(10) ])
'0123456789'

join doit prendre un itérable.

Apparemment, l'argument de join est [ str(_) for _ in xrange(10) ] et il s'agit d'une compréhension de list .

Regarde ça:

>>>''.join( str(_) for _ in xrange(10) )
'0123456789'

Maintenant, l'argument de join est simplement str(_) for _ in xrange(10), no [], mais le résultat est le même.

Pourquoi? str(_) for _ in xrange(10) produit-il également une liste ou une liste itérable?

65
Alcott
>>>''.join( str(_) for _ in xrange(10) )

Ceci s'appelle une expression generator, et est expliqué dans PEP 289 .

La principale différence entre les expressions du générateur et les compréhensions de liste est que les premiers ne créent pas la liste en mémoire.

Notez qu'il existe une troisième façon d'écrire l'expression:

''.join(map(str, xrange(10)))
51
NPE

Les autres répondants ont eu raison de répondre que vous aviez découvert un générateur d’expression (dont la notation est similaire à celle de la liste, mais sans les crochets).

En général, les genexps (comme on les surnomme affectueusement) sont plus efficaces en mémoire et plus rapides que la compréhension de listes.

CEPENDANT, dans le cas de ''.join(), une compréhension de liste est à la fois plus rapide et plus efficace en termes de mémoire. La raison en est que join doit effectuer deux passages sur les données, il a donc besoin d’une liste réelle. Si vous lui en donnez un, il peut commencer son travail immédiatement. Si vous lui donnez plutôt un genexp, il ne pourra pas commencer à travailler tant qu'il n'aura pas créé une nouvelle liste en mémoire en exécutant le genexp à épuisement:

~ $ python -m timeit '"".join(str(n) for n in xrange(1000))'
1000 loops, best of 3: 335 usec per loop
~ $ python -m timeit '"".join([str(n) for n in xrange(1000)])'
1000 loops, best of 3: 288 usec per loop

Le même résultat est valable lorsque l'on compare itertools.imap à map:

~ $ python -m timeit -s'from itertools import imap' '"".join(imap(str, xrange(1000)))'
1000 loops, best of 3: 220 usec per loop
~ $ python -m timeit '"".join(map(str, xrange(1000)))'
1000 loops, best of 3: 212 usec per loop
116
Raymond Hettinger

Votre deuxième exemple utilise une expression de générateur plutôt qu'une compréhension de liste. La différence est qu'avec la compréhension de liste, une liste est entièrement construite et passée à .join(). Avec l'expression du générateur, les éléments sont générés un par un et consommés par .join(). Ce dernier utilise moins de mémoire et est généralement plus rapide.

En l'occurrence, le constructeur de liste consommera avec plaisir toutes les éditables, y compris les expressions génératrices. Alors:

[str(n) for n in xrange(10)]

est juste "sucre syntaxique" pour:

list(str(n) for n in xrange(10))

En d'autres termes, une compréhension de liste est simplement une expression génératrice qui est transformée en une liste.

4
kindall

Si c'est entre parenthèses, mais pas entre crochets, c'est techniquement une expression génératrice. Les expressions de générateur ont été introduites pour la première fois dans Python 2.4.

http://wiki.python.org/moin/Generators

La partie après la jointure, ( str(_) for _ in xrange(10) ), est en elle-même une expression génératrice. Vous pourriez faire quelque chose comme:

mylist = (str(_) for _ in xrange(10))
''.join(mylist)

et cela signifie exactement la même chose que vous avez écrit dans le deuxième cas ci-dessus.

Les générateurs ont des propriétés très intéressantes, notamment parce qu’ils ne finissent pas par allouer une liste complète quand vous n’en avez pas besoin. Au lieu de cela, une fonction comme join "pompe" les éléments hors de l'expression du générateur, l'un après l'autre, en effectuant son travail sur les minuscules pièces intermédiaires.

Dans vos exemples particuliers, liste et générateur ne fonctionnent probablement pas très différemment, mais en général, je préfère utiliser des expressions de générateur (et même des fonctions de générateur) chaque fois que je le peux, principalement parce qu'il est extrêmement rare qu'un générateur soit lent qu'une matérialisation de liste complète.

3
sblom

Comme mentionné, il s’agit d’une expression générateur .

De la documentation:

Les parenthèses peuvent être omises lors d'appels avec un seul argument. Voir la section Appels pour plus de détails.

3
monkut

C'est un générateur, plutôt qu'une liste de compréhension. Les générateurs sont également itérables, mais plutôt que de créer d'abord la liste complète puis de la transmettre, elle transmet chaque valeur de xrange une par une, ce qui peut s'avérer beaucoup plus efficace.

0
Daniel Roseman

L'argument de votre deuxième appel join est une expression génératrice. Cela produit une valeur itérable.

0
Michael J. Barber