Question Python List Compréhension Vs. Carte


Y a-t-il une raison de préférer utiliser map() sur la compréhension de la liste ou vice versa? L'un ou l'autre est-il généralement plus efficace ou considéré généralement plus pythonique que l'autre?


536
2017-08-07 23:43


origine


Réponses:


map peut être microscopiquement plus rapide dans certains cas (lorsque vous ne faites pas un lambda dans le but, mais en utilisant la même fonction dans la carte et un listcomp). La compréhension des listes peut être plus rapide dans d'autres cas et la plupart des pythonistes (pas tous) les considèrent plus directe et plus claire.

Un exemple de l'avantage minime de la carte lorsque vous utilisez exactement la même fonction:

$ python -mtimeit -s'xs=range(10)' 'map(hex, xs)'
100000 loops, best of 3: 4.86 usec per loop
$ python -mtimeit -s'xs=range(10)' '[hex(x) for x in xs]'
100000 loops, best of 3: 5.58 usec per loop

Un exemple de la façon dont la comparaison de performance est complètement inversée lorsque la carte a besoin d'un lambda:

$ python -mtimeit -s'xs=range(10)' 'map(lambda x: x+2, xs)'
100000 loops, best of 3: 4.24 usec per loop
$ python -mtimeit -s'xs=range(10)' '[x+2 for x in xs]'
100000 loops, best of 3: 2.32 usec per loop

502
2017-08-07 23:45



Cas

  • Cas commun: Presque toujours, vous voudrez utiliser une compréhension de liste dans python car il sera plus évident de savoir ce que vous faites pour que les programmeurs débutants lisent votre code. (Cela ne s'applique pas à d'autres langages, où d'autres idiomes peuvent s'appliquer.) Ce que vous faites aux programmeurs python sera encore plus évident, puisque les compréhensions de liste sont le standard de facto dans python pour l'itération; elles sont attendu.
  • Cas moins commun: Cependant si vous ont déjà une fonction définie, il est souvent raisonnable d'utiliser map, bien qu'il soit considéré comme «non pythonique». Par exemple, map(sum, myLists) est plus élégant / laconique que [sum(x) for x in myLists]. Vous gagnez l'élégance de ne pas avoir à créer une variable fictive (par ex. sum(x) for x... ou sum(_) for _... ou sum(readableName) for readableName...) que vous devez taper deux fois, juste pour itérer. Le même argument est valable pour filter et reduce et tout de la itertools module: si vous avez déjà une fonction à portée de main, vous pouvez aller de l'avant et faire une programmation fonctionnelle. Cela améliore la lisibilité dans certaines situations, et la perd dans d'autres (par exemple les programmeurs débutants, les arguments multiples) ... mais la lisibilité de votre code dépend fortement de vos commentaires.
  • Presque jamais: Vous pouvez utiliser le map fonctionner comme une fonction abstraite pure tout en faisant la programmation fonctionnelle, où vous êtes mappage mapou currying map, ou profiter autrement de parler de map en tant que fonction. Dans Haskell par exemple, une interface de foncteur appelée fmap généralise le mappage sur toute structure de données. Ceci est très rare en python car la grammaire python vous oblige à utiliser le style générateur pour parler de l'itération; vous ne pouvez pas le généraliser facilement. (Ceci est parfois bon et parfois mauvais.) Vous pouvez probablement trouver des exemples de python rares où map(f, *lists) est une chose raisonnable à faire. L'exemple le plus proche que je puisse trouver serait sumEach = partial(map,sum), qui est un one-liner qui est très grossièrement équivalent à:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • Juste en utilisant un for-boucle: Vous pouvez bien sûr utiliser simplement une boucle for. Même si elles ne sont pas aussi élégantes du point de vue de la programmation fonctionnelle, les variables non locales rendent parfois le code plus clair dans les langages de programmation impératifs tels que python, car les gens sont très habitués à lire le code de cette façon. Les boucles For sont aussi, en général, les plus efficaces quand vous faites simplement une opération complexe qui ne construit pas une liste comme les listes-compréhensions et la carte sont optimisées pour (par exemple faire une somme, ou faire un arbre, etc.) - au moins efficace en termes de mémoire (pas nécessairement en termes de temps, où je m'attendrais au pire à un facteur constant, à part un hoquet pathologique rare de collection de déchets).

"Pythonisme"

Je n'aime pas le mot "pythonique" parce que je ne trouve pas que pythonic soit toujours élégant à mes yeux. Néanmoins, map et filter et des fonctions similaires (comme le très utile itertools module) sont probablement considérés comme non pythoniques en termes de style.

Paresse

En termes d'efficacité, comme la plupart des constructions de programmation fonctionnelle, La carte peut être paresseuse, et en fait est paresseux en python. Cela signifie que vous pouvez le faire (en python3) et votre ordinateur ne manquera pas de mémoire et perdra toutes vos données non sauvegardées:

>>> map(str, range(10**100))
<map object at 0x2201d50>

Essayez de le faire avec une compréhension de la liste:

>>> [str(n) for n in range(10**100)]
# DO NOT TRY THIS AT HOME OR YOU WILL BE SAD #

Notez que les listes de compréhension sont également intrinsèquement paresseuses, mais python a choisi de les implémenter comme non-paresseux. Néanmoins, python prend en charge les compréhensions de listes paresseuses sous la forme d'expressions de générateur, comme suit:

>>> (str(n) for n in range(10**100))
<generator object <genexpr> at 0xacbdef>

Vous pouvez essentiellement penser à la [...] syntaxe que de passer dans une expression de générateur au constructeur de la liste, comme list(x for x in range(5)).

Bref exemple artificiel

from operator import neg
print({x:x**2 for x in map(neg,range(5))})

print({x:x**2 for x in [-y for y in range(5)]})

print({x:x**2 for x in (-y for y in range(5))})

Les compréhensions de listes sont non-paresseuses, donc peuvent nécessiter plus de mémoire (à moins que vous n'utilisiez des compréhensions de générateurs). Les crochets [...] souvent rendre les choses évidentes, surtout quand dans un gâchis de parenthèses. D'un autre côté, parfois vous finissez par être bavard comme si vous tapiez [x for x in.... Tant que vous gardez vos variables d'itérateur à court, les compréhensions de liste sont généralement plus claires si vous ne mettez pas votre code en retrait. Mais vous pouvez toujours indenter votre code.

print(
    {x:x**2 for x in (-y for y in range(5))}
)

ou casser les choses:

rangeNeg5 = (-y for y in range(5))
print(
    {x:x**2 for x in rangeNeg5}
)

Comparaison d'efficacité pour python3

map est maintenant paresseux:

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=map(f,xs)'
1000000 loops, best of 3: 0.336 usec per loop            ^^^^^^^^^

Par conséquent, si vous n'utilisez pas toutes vos données, ou si vous ne savez pas à l'avance le nombre de données dont vous avez besoin, map dans python3 (et les expressions de générateur dans python2 ou python3) éviteront de calculer leurs valeurs jusqu'au dernier moment nécessaire. Habituellement, cela l'emportera généralement sur les frais généraux d'utilisation map. L'inconvénient est que c'est très limité en python par rapport à la plupart des langages fonctionnels: vous n'obtenez cet avantage que si vous accédez à vos données de gauche à droite "dans l'ordre", car les expressions de générateur python ne peuvent être évaluées que x[0], x[1], x[2], ....

Cependant, disons que nous avons une fonction pré-faite f nous aimerions map, et nous ignorons la paresse de map en forçant immédiatement l'évaluation avec list(...). Nous obtenons des résultats très intéressants:

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(map(f,xs))'                                                                                                                                                
10000 loops, best of 3: 165/124/135 usec per loop        ^^^^^^^^^^^^^^^
                    for list(<map object>)

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=[f(x) for x in xs]'                                                                                                                                      
10000 loops, best of 3: 181/118/123 usec per loop        ^^^^^^^^^^^^^^^^^^
                    for list(<generator>), probably optimized

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(f(x) for x in xs)'                                                                                                                                    
1000 loops, best of 3: 215/150/150 usec per loop         ^^^^^^^^^^^^^^^^^^^^^^
                    for list(<generator>)

Dans les résultats sont sous la forme AAA / BBB / CCC où A a été effectuée avec sur un poste de travail Intel circa-2010 avec python 3.?.?, Et B et C ont été effectuées avec un poste de travail AMD circa-2013 avec python 3.2.1, avec un matériel extrêmement différent. Le résultat semble être que la compréhension des cartes et des listes est comparable en termes de performance, ce qui est le plus fortement influencé par d'autres facteurs aléatoires. La seule chose que nous pouvons dire semble être, curieusement, alors que nous attendons des listes de compréhension [...] pour effectuer mieux que les expressions de générateur (...), map est AUSSI plus efficace que les expressions de générateur (en supposant à nouveau que toutes les valeurs sont évaluées / utilisées).

Il est important de réaliser que ces tests assument une fonction très simple (la fonction d'identité); cependant, cela est bien, car si la fonction était compliquée, les frais généraux de performance seraient négligeables par rapport aux autres facteurs du programme. (Il peut encore être intéressant de tester avec d'autres choses simples comme f=lambda x:x+x)

Si vous êtes qualifié pour lire l'assemblage python, vous pouvez utiliser le dis module pour voir si c'est en fait ce qui se passe dans les coulisses:

>>> listComp = compile('[f(x) for x in xs]', 'listComp', 'eval')
>>> dis.dis(listComp)
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x2511a48, file "listComp", line 1>) 
              3 MAKE_FUNCTION            0 
              6 LOAD_NAME                0 (xs) 
              9 GET_ITER             
             10 CALL_FUNCTION            1 
             13 RETURN_VALUE         
>>> listComp.co_consts
(<code object <listcomp> at 0x2511a48, file "listComp", line 1>,)
>>> dis.dis(listComp.co_consts[0])
  1           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                18 (to 27) 
              9 STORE_FAST               1 (x) 
             12 LOAD_GLOBAL              0 (f) 
             15 LOAD_FAST                1 (x) 
             18 CALL_FUNCTION            1 
             21 LIST_APPEND              2 
             24 JUMP_ABSOLUTE            6 
        >>   27 RETURN_VALUE

>>> listComp2 = compile('list(f(x) for x in xs)', 'listComp2', 'eval')
>>> dis.dis(listComp2)
  1           0 LOAD_NAME                0 (list) 
              3 LOAD_CONST               0 (<code object <genexpr> at 0x255bc68, file "listComp2", line 1>) 
              6 MAKE_FUNCTION            0 
              9 LOAD_NAME                1 (xs) 
             12 GET_ITER             
             13 CALL_FUNCTION            1 
             16 CALL_FUNCTION            1 
             19 RETURN_VALUE         
>>> listComp2.co_consts
(<code object <genexpr> at 0x255bc68, file "listComp2", line 1>,)
>>> dis.dis(listComp2.co_consts[0])
  1           0 LOAD_FAST                0 (.0) 
        >>    3 FOR_ITER                17 (to 23) 
              6 STORE_FAST               1 (x) 
              9 LOAD_GLOBAL              0 (f) 
             12 LOAD_FAST                1 (x) 
             15 CALL_FUNCTION            1 
             18 YIELD_VALUE          
             19 POP_TOP              
             20 JUMP_ABSOLUTE            3 
        >>   23 LOAD_CONST               0 (None) 
             26 RETURN_VALUE

>>> evalledMap = compile('list(map(f,xs))', 'evalledMap', 'eval')
>>> dis.dis(evalledMap)
  1           0 LOAD_NAME                0 (list) 
              3 LOAD_NAME                1 (map) 
              6 LOAD_NAME                2 (f) 
              9 LOAD_NAME                3 (xs) 
             12 CALL_FUNCTION            2 
             15 CALL_FUNCTION            1 
             18 RETURN_VALUE 

Il semble qu'il vaut mieux utiliser [...] syntaxe que list(...). Malheureusement, le map La classe est un peu opaque au démontage, mais nous pouvons faire en raison de notre test de vitesse.


359
2018-06-20 05:41



Tu devrais utiliser map et filter au lieu de la liste des compréhensions.

Un objectif raison pour laquelle vous devriez les préférer même si elles ne sont pas "Pythonic" est la suivante:
Ils nécessitent des fonctions / lambdas comme arguments, qui introduire une nouvelle portée.

Je me suis fait mordre par ça plus d'une fois:

for x, y in somePoints:
    # (several lines of code here)
    squared = [x ** 2 for x in numbers]
    # Oops, x was silently overwritten!

mais si à la place j'avais dit:

for x, y in somePoints:
    # (several lines of code here)
    squared = map(lambda x: x ** 2, numbers)

alors tout aurait été bien.

Vous pourriez dire que j'étais bête d'utiliser le même nom de variable dans la même portée.

Je n'étais pas. Le code était bon à l'origine - les deux xs n'étaient pas dans le même champ d'application.
C'était seulement après déplacé le bloc interne à une section différente du code que le problème est survenu (lire: problème lors de la maintenance, pas de développement), et je ne m'y attendais pas.

Oui, si vous ne faites jamais cette erreur alors les listes de compréhension sont plus élégantes.
Mais d'après mon expérience personnelle (et de voir les autres faire la même erreur), je l'ai vu arriver assez de fois que je pense que ça ne vaut pas la douleur que vous avez à traverser lorsque ces bugs s'insinuent dans votre code.

Conclusion:

Utilisation map et filter. Ils évitent les bogues subtils difficiles à diagnostiquer.

Note latérale:

Ne pas oublier d'envisager d'utiliser imap et ifilter (dans itertools) si elles sont appropriées à votre situation!


76
2017-11-20 22:28



Réellement, map et les compréhensions de listes se comportent différemment dans le langage Python 3. Jetez un oeil au programme Python 3 suivant:

def square(x):
    return x*x
squares = map(square, [1, 2, 3])
print(list(squares))
print(list(squares))

Vous pouvez vous attendre à imprimer la ligne "[1, 4, 9]" deux fois, mais à la place, il imprime "[1, 4, 9]" suivi de "[]". La première fois que vous regardez squares il semble se comporter comme une séquence de trois éléments, mais la deuxième fois comme un vide.

Dans la langue Python 2 map renvoie une vieille liste simple, tout comme la compréhension de liste dans les deux langues. Le point crucial est que la valeur de retour de map en Python 3 (et imap en Python 2) n'est pas une liste - c'est un itérateur!

Les éléments sont consommés lorsque vous parcourez un itérateur contrairement à lorsque vous parcourez une liste. C'est pourquoi squares semble vide dans la dernière print(list(squares)) ligne.

Résumer:

  • Lorsque vous traitez avec des itérateurs, vous devez vous rappeler qu'ils sont dynamiques et qu'ils se transforment lorsque vous les traversez.
  • Les listes sont plus prévisibles puisqu'elles ne changent que lorsque vous les mutez explicitement; elles sont conteneurs.
  • Et un bonus: les nombres, les chaînes et les tuples sont encore plus prévisibles puisqu'ils ne peuvent pas changer du tout; elles sont valeurs.

33
2017-10-01 13:09



Je trouve que la compréhension des listes est généralement plus expressive de ce que j'essaie de faire que map - ils le font tous les deux, mais le premier sauve la charge mentale d'essayer de comprendre ce qui pourrait être un complexe lambdaexpression.

Il y a aussi une interview quelque part (je ne peux pas le trouver) où les listes de Guido lambdas et les fonctions fonctionnelles comme la chose qu'il regrette le plus d'accepter en Python, donc vous pouvez faire l'argument qu'ils sont non-Pythonic en vertu de cela.


15
2017-08-07 23:59



Voici un cas possible:

map(lambda op1,op2: op1*op2, list1, list2)

contre:

[op1*op2 for op1,op2 in zip(list1,list2)]

Je devine que le zip () est un overhead malheureux et inutile que vous devez vous livrer si vous insistez sur l'utilisation de la compréhension de la liste au lieu de la carte. Ce serait génial si quelqu'un clarifiait cela, que ce soit affirmativement ou négativement.


12
2017-11-02 08:42



Si vous envisagez d'écrire un code asynchrone, parallèle ou distribué, vous préférerez probablement map sur une compréhension de liste - comme la plupart des paquets asynchrones, parallèles ou distribués map fonction pour surcharger python map. Puis en passant le bon map Fonctionnez pour le reste de votre code, vous ne devrez peut-être pas modifier votre code de série original pour le faire fonctionner en parallèle (etc).


12
2018-06-08 17:03



Une autre raison d'utiliser la compréhension de liste sur map () et filter () est que Psyco ne peut pas compiler ces fonctions.

Voir http://psyco.sourceforge.net/


6
2018-02-21 21:27



Donc, depuis Python 3, map() est un itérateur, vous devez garder à l'esprit ce dont vous avez besoin: un itérateur ou list objet.

Comme @AlexMartelli déjà mentionné, map() est plus rapide que la compréhension de la liste seulement si vous n'utilisez pas lambda fonction.

Je vais vous présenter quelques comparaisons de temps.

Python 3.5.2 et CPython
J'ai utilisé Cahier de Jupiter et particulièrement %timeit commande magique intégrée
Des mesures: s == 1000 ms == 1000 * 1000 μs = 1000 * 1000 * 1000 ns

Installer:

x_list = [(i, i+1, i+2, i*2, i-9) for i in range(1000)]
i_list = list(range(1000))

Fonction intégrée:

%timeit map(sum, x_list)  # creating iterator object
# Output: The slowest run took 9.91 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 277 ns per loop

%timeit list(map(sum, x_list))  # creating list with map
# Output: 1000 loops, best of 3: 214 µs per loop

%timeit [sum(x) for x in x_list]  # creating list with list comprehension
# Output: 1000 loops, best of 3: 290 µs per loop

lambda fonction:

%timeit map(lambda i: i+1, i_list)
# Output: The slowest run took 8.64 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 325 ns per loop

%timeit list(map(lambda i: i+1, i_list))
# Output: 1000 loops, best of 3: 183 µs per loop

%timeit [i+1 for i in i_list]
# Output: 10000 loops, best of 3: 84.2 µs per loop

Il y a aussi une telle chose comme l'expression du générateur, voir PEP-0289. Donc j'ai pensé qu'il serait utile de l'ajouter à la comparaison

%timeit (sum(i) for i in x_list)
# Output: The slowest run took 6.66 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 495 ns per loop

%timeit list((sum(x) for x in x_list))
# Output: 1000 loops, best of 3: 319 µs per loop

%timeit (i+1 for i in i_list)
# Output: The slowest run took 6.83 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 506 ns per loop

%timeit list((i+1 for i in i_list))
# Output: 10000 loops, best of 3: 125 µs per loop

Vous avez besoin list objet:

Utilisez la compréhension de la liste si c'est une fonction personnalisée, utilisez list(map()) s'il y a une fonction intégrée

Vous n'avez pas besoin list objet, vous avez juste besoin d'un itérable:

Toujours utiliser map()!


5
2017-12-03 14:18



Je considère que la façon la plus pythonienne est d'utiliser une compréhension de liste au lieu de map et filter. La raison en est que la compréhension des listes est plus claire que map et filter.

In [1]: odd_cubes = [x ** 3 for x in range(10) if x % 2 == 1] # using a list comprehension

In [2]: odd_cubes_alt = list(map(lambda x: x ** 3, filter(lambda x: x % 2 == 1, range(10)))) # using map and filter

In [3]: odd_cubes == odd_cubes_alt
Out[3]: True

Comme vous le voyez, une compréhension ne nécessite pas de supplément lambda expressions comme map Besoins. De plus, une compréhension permet également de filtrer facilement map a besoin filter pour permettre le filtrage.


0
2017-09-04 17:20