Compréhension des listes vs carte

733

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

TimothyAWiseman
la source
8
Notez que PyLint vous avertit si vous utilisez la carte au lieu de la compréhension de liste, voir le message W0141 .
lumbric
2
@lumbric, je ne suis pas sûr, mais cela ne fonctionne que si lambda est utilisé dans la carte.
0xc0de

Réponses:

662

mappeut être microscopiquement plus rapide dans certains cas (lorsque vous ne faites PAS de lambda à cet effet, mais utilisez la même fonction dans map et listcomp). La compréhension des listes peut être plus rapide dans d'autres cas et la plupart (pas tous) les pythonistes les considèrent plus directes et plus claires.

Un exemple de l'avantage de vitesse minuscule 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 des performances 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
Alex Martelli
la source
39
Oui, en effet, notre guide de style Python interne au travail recommande explicitement les listcomps contre la carte et le filtre (sans même mentionner la carte d'amélioration des performances minuscule mais mesurable peut donner dans certains cas ;-).
Alex Martelli
46
Ne pas kibash sur les points de style infinis d'Alex, mais parfois la carte me semble plus facile à lire: data = map (str, some_list_of_objects). Quelques autres ... operator.attrgetter, operator.itemgetter, etc.
Gregg Lind
57
map(operator.attrgetter('foo'), objs)plus facile à lire que [o.foo for o in objs]?!
Alex Martelli
52
@Alex: Je préfère ne pas introduire de noms inutiles, comme oici, et vos exemples montrent pourquoi.
Reid Barton
29
Je pense que @GreggLind a un point, avec son str()exemple, cependant.
Eric O Lebigot
474

Étuis

  • Cas commun : Presque toujours, vous voudrez utiliser une compréhension de liste en python, car ce que vous faites aux programmeurs débutants lisant votre code sera plus évident. (Cela ne s'applique pas aux autres langages, où d'autres idiomes peuvent s'appliquer.) Ce que vous faites aux programmeurs python sera encore plus évident, car les listes de compréhension sont la norme de facto en python pour l'itération; ils sont attendus .
  • Cas moins courant : Cependant, si vous avez déjà une fonction définie , elle est souvent raisonnable à utiliser map, bien qu'elle soit considérée 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 exemple 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 vaut pour filteret reducen'importe quoi du itertoolsmodule: si vous avez déjà une fonction à portée de main, vous pouvez continuer et faire de la programmation fonctionnelle. Cela gagne en lisibilité dans certaines situations, et le perd dans d'autres (par exemple les programmeurs novices, les arguments multiples) ... mais la lisibilité de votre code dépend de toute façon de vos commentaires.
  • Presque jamais : vous souhaiterez peut-être utiliser la mapfonction comme une fonction abstraite pure lors de la programmation fonctionnelle, où vous mappez map, ou curry map, ou autrement bénéficier de parler mapcomme une fonction. Dans Haskell par exemple, une interface de foncteur appelée fmapgénéralise le mappage sur n'importe quelle structure de données. C'est très rare en python car la grammaire python vous oblige à utiliser le style générateur pour parler d'itération; vous ne pouvez pas le généraliser facilement. (C'est parfois bon et parfois mauvais.) Vous pouvez probablement trouver de rares exemples de python où il map(f, *lists)est raisonnable de faire. L'exemple le plus proche que je peux trouver serait sumEach = partial(map,sum), qui est une doublure qui est à peu près équivalente à:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • Utiliser simplement une forboucle : vous pouvez bien sûr également utiliser une boucle for. Bien qu'elles ne soient 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 également, généralement, les plus efficaces lorsque vous effectuez simplement une opération complexe qui ne construit pas de liste, comme les listes de compréhension et la carte sont optimisées (par exemple, sommation ou création d'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, sauf quelques rares hoquets pathologiques de collecte des ordures).

"Pythonisme"

Je n'aime pas le mot "pythonic" car je ne trouve pas que le pythonic soit toujours élégant à mes yeux. Néanmoins, mapet filteret des fonctions similaires (comme le itertoolsmodule très utile ) sont probablement considérées comme non pythoniques en termes de style.

Paresse

En termes d'efficacité, comme la plupart des constructions de programmation fonctionnelles, MAP PEUT ÊTRE LAZY , et en fait est paresseux en python. Cela signifie que vous pouvez le faire (en python3 ) et que votre ordinateur ne manquera pas de mémoire et perdra toutes vos données non enregistrées:

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

Essayez de faire cela 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 compréhensions de liste sont également intrinsèquement paresseuses, mais python a choisi de les implémenter comme non paresseuses . Néanmoins, python prend en charge les compréhensions de liste paresseuse 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 comme passant une expression de générateur au constructeur de 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 liste ne sont pas paresseuses, elles peuvent donc nécessiter plus de mémoire (sauf si vous utilisez des compréhensions de générateur). Les crochets [...]rendent souvent les choses évidentes, surtout en cas de confusion entre parenthèses. D'un autre côté, vous finissez parfois par être verbeux comme pour taper [x for x in.... Tant que vous gardez vos variables d'itérateur courtes, les compréhensions de liste sont généralement plus claires si vous n'indentez pas votre code. Mais vous pouvez toujours mettre en retrait 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 de combien de données vous avez besoin, mapen python3 (et les expressions de générateur en python2 ou python3) éviteront de calculer leurs valeurs jusqu'au dernier moment nécessaire. Habituellement, cela dépassera généralement les frais généraux liés à l'utilisation map. L'inconvénient est que cela est très limité en python contrairement à 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 du générateur de python ne peuvent être évaluées que dans l'ordre x[0], x[1], x[2], ....

Cependant, disons que nous avons une fonction prédéfinie que fnous aimerions map, et nous ignorons la paresse mapen 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>)

Les résultats sont sous la forme AAA / BBB / CCC où A a été exécuté avec sur un poste de travail Intel vers 2010 avec python 3.?.?, Et B et C ont été exécutés avec un poste de travail AMD vers 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 performances, ce qui est le plus fortement affecté par d'autres facteurs aléatoires. La seule chose que nous pouvons dire semble être que, étrangement, alors que nous nous attendons à ce que les compréhensions de liste [...]fonctionnent mieux que les expressions de générateur (...), elles mapsont également plus efficaces 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, c'est bien parce que si la fonction était compliquée, les frais généraux de performance seraient négligeables par rapport à d'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 habile à lire l'assemblage python, vous pouvez utiliser le dismodule pour voir si c'est réellement 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 la [...]syntaxe que list(...). Malheureusement, la mapclasse est un peu opaque au démontage, mais nous pouvons le faire avec notre test de vitesse.

ninjagecko
la source
5
"le module itertools très utile [est] probablement considéré comme non pythonique en termes de style". Hmm. Je n'aime pas non plus le terme "Pythonic", donc dans un certain sens je ne me soucie pas de ce qu'il signifie, mais je ne pense pas qu'il soit juste pour ceux qui l'utilisent, de dire que selon "Pythonicness" mapet filterainsi que la bibliothèque standard itertoolssont intrinsèquement mauvais style. À moins que GvR ne dise que c'était une erreur terrible ou uniquement pour la performance, la seule conclusion naturelle si c'est ce que dit "Pythonicness" est de l'oublier comme stupide ;-)
Steve Jessop
4
@SteveJessop: En fait, Guido pensait que laisser tomber map/ filterétait une excellente idée pour Python 3 , et seule une rébellion par d'autres Pythonistas les a conservés dans l'espace de noms intégré (alors qu'il a reduceété déplacé vers functools). Personnellement, je ne suis pas d'accord ( mapet je suis d'accord filteravec les fonctions prédéfinies, en particulier intégrées, ne les utilisez jamais si un lambdaest nécessaire), mais GvR les a fondamentalement appelées non Pythonic depuis des années.
ShadowRanger
@ShadowRanger: vrai, mais le GvR a-t-il déjà envisagé de le supprimer itertools? La partie que je cite de cette réponse est la principale affirmation qui m'étouffe. Je ne sais pas si dans son monde idéal, mapet filterje passerais à itertools(ou functools) ou irais entièrement, mais quel que soit le cas, une fois que l'on dit que itertoolsc'est non Pythonique dans son intégralité, alors je ne sais pas vraiment ce qu'est "Pythonique" censé signifier, mais je ne pense pas que cela puisse être quelque chose de similaire à "ce que GvR recommande aux gens d'utiliser".
Steve Jessop
2
@SteveJessop: Je ne m'adressais qu'à map/ filter, non itertools. La programmation fonctionnelle est parfaitement Pythonique ( itertools, functoolset operatoront toutes été conçues spécifiquement avec la programmation fonctionnelle à l'esprit, et j'utilise des idiomes fonctionnels en Python tout le temps), et itertoolsfournit des fonctionnalités qui seraient difficiles à mettre en œuvre, c'est spécifiquement mapet filterétant redondant avec des expressions de générateur qui a incité Guido à les détester. itertoolsa toujours été bien.
ShadowRanger
1
Je pourrais préférer cette réponse s'il y avait un moyen. Bien expliqué.
NelsonGon
95

Python 2: vous devez utiliser mapet filterau lieu des listes de compréhension.

Une raison objective pour laquelle vous devriez les préférer même s'ils ne sont pas "Pythonic" est la suivante:
ils nécessitent des fonctions / lambdas comme arguments, ce qui introduit une nouvelle portée .

J'ai été mordu par 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 stupide d'utiliser le même nom de variable dans la même portée.

Je ne l'étais pas. Le code était bien à l'origine - les deux xn'étaient pas dans la même portée.
Ce n'est qu'après avoir déplacé le bloc interne vers une autre section du code que le problème est apparu (lire: problème pendant la maintenance, pas le développement), et je ne m'y attendais pas.

Oui, si vous ne faites jamais cette erreur, les listes de compréhension sont plus élégantes.
Mais par expérience personnelle (et en voyant les autres commettre la même erreur), je l'ai vu se produire suffisamment de fois que je pense que cela ne vaut pas la peine que vous devez endurer lorsque ces bogues se glissent dans votre code.

Conclusion:

Utilisez mapet filter. Ils empêchent les bogues subtils difficiles à diagnostiquer liés à la portée.

Note latérale:

N'oubliez pas d'envisager d'utiliser imapet ifilter(in itertools) s'ils conviennent à votre situation!

user541686
la source
7
Merci de l'avoir signalé. Il ne m'était pas venu à l'esprit que la compréhension de la liste avait la même portée et pouvait être un problème. Cela dit, je pense que certaines des autres réponses indiquent clairement que la compréhension de la liste devrait être l'approche par défaut la plupart du temps, mais que c'est quelque chose à retenir. C'est également un bon rappel général pour garder les fonctions (et donc la portée) petites et avoir des tests unitaires approfondis et utiliser des instructions assert.
TimothyAWiseman
13
@wim: Il ne s'agissait que de Python 2, bien qu'il s'applique à Python 3 si vous souhaitez rester rétrocompatible. Je le savais et j'utilisais Python depuis un moment maintenant (oui, plus que quelques mois), et pourtant cela m'est arrivé. J'en ai vu d'autres plus intelligents que moi tomber dans le même piège. Si vous êtes si brillant et / ou expérimenté que ce n'est pas un problème pour vous, alors je suis heureux pour vous, je ne pense pas que la plupart des gens sont comme vous. S'ils l'étaient, il n'y aurait pas une telle envie de le réparer dans Python 3.
user541686
12
Je suis désolé, mais vous l'avez écrit fin 2012, bien après que python 3 soit sur la scène, et la réponse se lit comme si vous recommandiez un style de codage python autrement impopulaire juste parce que vous avez été mordu par un bogue lors de la découpe et ... coller du code. Je n'ai jamais prétendu être brillant ou expérimenté, je ne suis tout simplement pas d'accord pour dire que cette affirmation audacieuse est justifiée par vos raisons.
wim
8
@wim: Hein? Python 2 est toujours utilisé dans de nombreux endroits, le fait que Python 3 existe ne change rien à cela. Et quand vous dites "ce n'est pas exactement un bug subtil pour quiconque utilise Python depuis plus de quelques mois", cette phrase signifie littéralement "cela ne concerne que les développeurs inexpérimentés" (clairement pas vous). Et pour mémoire, vous n'avez clairement pas lu la réponse parce que j'ai dit en gras que je déplaçais , pas en copiant, du code. Les bogues de copier-coller sont assez uniformes dans toutes les langues. Ce type de bogue est plus spécifique à Python en raison de sa portée; c'est plus subtil et plus facile à oublier et à manquer.
user541686
3
Ce n'est toujours pas une raison logique pour passer à mapet / ou filter. Si quoi que ce soit, la traduction la plus directe et la plus logique pour éviter votre problème n'est pas de, map(lambda x: x ** 2, numbers)mais plutôt une expression de générateur list(x ** 2 for x in numbers)qui ne fuit pas, comme JeromeJ l'a déjà souligné. Écoutez Mehrdad, ne prenez pas un vote négatif si personnellement, je suis tout simplement en désaccord avec votre raisonnement ici.
wim
46

En fait, mapet les compréhensions de liste se comportent très différemment dans le langage Python 3. Jetez un œil 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 à ce qu'il imprime la ligne "[1, 4, 9]" deux fois, mais à la place, il imprime "[1, 4, 9]" suivi de "[]". La première fois que vous la regardez, squareselle semble se comporter comme une séquence de trois éléments, mais la deuxième fois comme une séquence vide.

Dans le langage Python 2, maprenvoie une ancienne liste simple, tout comme les compréhensions de liste dans les deux langues. Le point crucial est que la valeur de retour de mapPython 3 (et imapPython 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 squaressemble 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 avec état et qu'ils mutent lorsque vous les traversez.
  • Les listes sont plus prévisibles car elles ne changent que lorsque vous les mutez explicitement; ce sont des conteneurs .
  • Et un bonus: les nombres, les chaînes et les tuples sont encore plus prévisibles car ils ne peuvent pas changer du tout; ce sont des valeurs .
raek
la source
c'est probablement le meilleur argument pour la compréhension des listes. la carte pythons n'est pas la carte fonctionnelle mais le beau-fils paralysé à tête rouge d'une implémentation fonctionnelle. Très triste, car je n'aime vraiment pas les compréhensions.
semiomant
@semiomant Je dirais qu'une carte paresseuse (comme en python3) est plus «fonctionnelle» qu'une carte désireuse (comme en python2). Par exemple, la carte dans Haskell est paresseuse (enfin, tout dans Haskell est paresseux ...). Quoi qu'il en soit, la carte paresseuse est meilleure pour chaîner des cartes - si vous avez une carte appliquée à la carte appliquée à la carte, vous avez une liste pour chacun des appels de carte intermédiaires en python2, tandis qu'en python3 vous n'avez qu'une seule liste résultante, donc sa mémoire est plus efficace .
MnZrK
Je suppose que ce que je veux, c'est mapproduire une structure de données, pas un itérateur. Mais les itérateurs paresseux sont peut-être plus faciles que les structures de données paresseuses. Nourriture pour la pensée. Merci @MnZrK
semiomant
Vous voulez dire que la carte renvoie un itérable, pas un itérateur.
user541686
16

Je trouve que la compréhension des listes est généralement plus expressive de ce que j'essaie de faire que map- elles le font toutes les deux, mais la première économise la charge mentale d'essayer de comprendre ce qui pourrait être une lambdaexpression complexe .

Il y a aussi une interview quelque part (je ne peux pas la trouver par hasard) où Guido répertorie lambdas et les fonctions fonctionnelles comme ce qu'il regrette le plus d'accepter en Python, vous pouvez donc faire valoir qu'ils sont non-pythoniques en vertu de ça.

Dan
la source
9
Ouais, soupir, mais l'intention initiale de Guido de supprimer complètement lambda dans Python 3 a fait l'objet d'un barrage de lobbying contre, alors il y est retourné malgré mon solide soutien - ah bien, devinez que lambda est juste trop pratique dans de nombreux cas SIMPLES , le seul le problème est quand il dépasse les limites de SIMPLE ou est assigné à un nom (dans ce dernier cas, c'est un duplicat idiot entravé de def! -).
Alex Martelli
1
L'entrevue à laquelle vous pensez est celle-ci: amk.ca/python/writing/gvr-interview , où Guido dit "Parfois, j'ai été trop rapide pour accepter des contributions, et j'ai réalisé plus tard que c'était une erreur. Un exemple serait certaines des fonctionnalités de programmation fonctionnelle, telles que les fonctions lambda. lambda est un mot clé qui vous permet de créer une petite fonction anonyme; des fonctions intégrées telles que mapper, filtrer et réduire exécuter une fonction sur un type de séquence, comme une liste. "
J.Taylor
3
@Alex, je n'ai pas vos années d'expérience, mais j'ai vu des compréhensions de listes beaucoup plus compliquées que des lambdas. Bien sûr, abuser des fonctionnalités linguistiques est toujours une tentation difficile de résister. Il est intéressant de noter que les compréhensions de listes (empiriquement) semblent plus sujettes aux abus que les lambdas, bien que je ne sache pas pourquoi cela devrait être le cas. Je soulignerai également que "clopiner" n'est pas toujours une mauvaise chose. Réduire la portée des «choses que cette ligne pourrait faire» peut parfois faciliter la tâche du lecteur. Par exemple, le constmot - clé en C ++ est un grand triomphe dans ce sens.
Stuart Berg
> guido. Ce qui est une autre preuve que Guido est hors de son esprit. Bien sûr lambda, ils ont été rendus si boiteux (pas de déclarations ..) qu'ils sont difficiles à utiliser et limités de toute façon.
javadba
16

Voici un cas possible:

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

contre:

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

Je suppose que le zip () est une surcharge malheureuse et inutile que vous devez vous livrer si vous insistez pour utiliser des listes de compréhension au lieu de la carte. Ce serait formidable si quelqu'un clarifie cela, que ce soit de manière affirmative ou négative.

Andz
la source
"[op1 * op2 from op1, op2 in zip (list1, list2)]" | s / form / for / Et une liste équivalente sans zip: (moins lisible) [list1 [i] * list2 [i] pour i dans la plage (len (list1))]
faible
2
Doit être "pour" pas "de" dans votre deuxième citation de code, @andz, et dans le commentaire de @ faiblesseish aussi. Je pensais avoir découvert une nouvelle approche syntaxique pour la compréhension des listes ... Darn.
physicsmichael
4
pour ajouter un commentaire très tardif, vous pouvez zipparesseux en utilisantitertools.izip
tacaswell
5
Je pense que je préfère encore map(operator.mul, list1, list2). C'est sur ces expressions très simples du côté gauche que les compréhensions deviennent maladroites.
Yann Vernier
1
Je n'avais pas réalisé que la carte pouvait prendre plusieurs itérables comme entrées pour sa fonction et pouvait ainsi éviter un zip.
bli
16

Si vous envisagez d'écrire du code asynchrone, parallèle ou distribué, vous préférerez probablement mapune compréhension de liste - car la plupart des packages asynchrones, parallèles ou distribués fournissent une mapfonction pour surcharger python map. Ensuite, en transmettant la mapfonction appropriée au reste de votre code, vous n'aurez peut-être pas à modifier votre code de série d'origine pour qu'il s'exécute en parallèle (etc.).

Mike McKerns
la source
1
Le module de multiprocessing de Python fait ceci: docs.python.org/2/library/multiprocessing.html
Robert L.
9

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

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

Je vais vous présenter quelques comparaisons temporelles.

Python 3.5.2 et CPython
J'ai utilisé le bloc - notes Jupiter et en particulier %timeitla commande magique intégrée
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 une 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 existe également une expression de générateur, voir PEP-0289 . J'ai donc 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 d'un listobjet:

Utilisez la compréhension de 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 d' listobjet, vous avez juste besoin d'un objet itérable:

Utilisez toujours map()!

vishes_shell
la source
1

J'ai effectué un test rapide comparant trois méthodes pour invoquer la méthode d'un objet. La différence de temps, dans ce cas, est négligeable et dépend de la fonction en question (voir la réponse de @Alex Martelli ). Ici, j'ai regardé les méthodes suivantes:

# map_lambda
list(map(lambda x: x.add(), vals))

# map_operator
from operator import methodcaller
list(map(methodcaller("add"), vals))

# map_comprehension
[x.add() for x in vals]

J'ai regardé les listes (stockées dans la variable vals) des nombres entiers (Python int) et des nombres à virgule flottante (Python float) pour augmenter la taille des listes. La classe factice suivante DummyNumest considérée:

class DummyNum(object):
    """Dummy class"""
    __slots__ = 'n',

    def __init__(self, n):
        self.n = n

    def add(self):
        self.n += 5

Plus précisément, la addméthode. L' __slots__attribut est une optimisation simple en Python pour définir la mémoire totale nécessaire à la classe (attributs), réduisant la taille de la mémoire. Voici les graphiques résultants.

Performances du mappage des méthodes d'objets Python

Comme indiqué précédemment, la technique utilisée fait une différence minimale et vous devez coder de la manière la plus lisible pour vous, ou dans les circonstances particulières. Dans ce cas, la compréhension de liste ( map_comprehensiontechnique) est la plus rapide pour les deux types d'ajouts dans un objet, en particulier avec des listes plus courtes.

Visitez cette boîte à pâte pour la source utilisée pour générer le tracé et les données.

craymichael
la source
1
Comme déjà expliqué dans d'autres réponses, mapn'est plus rapide que si la fonction est appelée exactement de la même manière (c'est-à-dire [*map(f, vals)]vs. [f(x) for x in vals]). C'est donc list(map(methodcaller("add"), vals))plus rapide que [methodcaller("add")(x) for x in vals]. mappeut ne pas être plus rapide lorsque l'homologue en boucle utilise une méthode d'appel différente qui peut éviter une surcharge (par exemple, x.add()évite la methodcallersurcharge de l'expression ou lambda). Pour ce cas de test spécifique, [*map(DummyNum.add, vals)]serait plus rapide (car DummyNum.add(x)et x.add()aurait essentiellement les mêmes performances).
GZ0
1
Soit dit en passant, les list()appels explicites sont légèrement plus lents que les compréhensions de liste. Pour une comparaison juste, vous devez écrire [*map(...)].
GZ0
@ GZ0 merci pour les bons commentaires! Tout est logique, et je ne savais pas que les list()appels augmentaient les frais généraux. J'aurais dû passer plus de temps à lire les réponses. Je vais relancer ces tests pour une comparaison équitable, même si les différences peuvent être négligeables.
craymichael
0

Je considère que la façon la plus Pythonique est d'utiliser une compréhension de liste au lieu de mapet filter. La raison en est que les listes de compréhension sont plus claires que mapet 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 d' lambdaexpressions supplémentaires selon les mapbesoins. En outre, une compréhension permet également filtrer facilement, tout en mapexige filterpour permettre le filtrage.

lmiguelvargasf
la source
0

J'ai essayé le code par @ alex-martelli mais j'ai trouvé des différences

python -mtimeit -s "xs=range(123456)" "map(hex, xs)"
1000000 loops, best of 5: 218 nsec per loop
python -mtimeit -s "xs=range(123456)" "[hex(x) for x in xs]"
10 loops, best of 5: 19.4 msec per loop

La carte prend le même temps, même pour de très grandes plages, tandis que l'utilisation de la compréhension de liste prend beaucoup de temps, comme le montre mon code. Donc, en plus d'être considéré comme "non-pythonique", je n'ai rencontré aucun problème de performance lié à l'utilisation de la carte.

Mohit Raj
la source
3
Il s'agit d'une très vieille question, et la réponse à laquelle vous faites référence a très probablement été écrite en référence à Python 2, où maprenvoie une liste. En Python 3, mapest évalué paresseusement, donc simplement appeler mapne calcule aucun des nouveaux éléments de la liste, d'où la raison pour laquelle vous obtenez des temps si courts.
kaya3
Je pense que vous utilisez Python 3.x Quand j'ai posé cette question, Python 3 n'était sorti que récemment et Python 2.x était vraiment la norme.
TimothyAWiseman