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?
python
list-comprehension
map-function
TimothyAWiseman
la source
la source
Réponses:
map
peut ê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:
Un exemple de la façon dont la comparaison des performances est complètement inversée lorsque la carte a besoin d'un lambda:
la source
map(operator.attrgetter('foo'), objs)
plus facile à lire que[o.foo for o in objs]
?!o
ici, et vos exemples montrent pourquoi.str()
exemple, cependant.Étuis
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 exemplesum(x) for x...
ousum(_) for _...
ousum(readableName) for readableName...
) que vous devez taper deux fois, juste pour itérer. Le même argument vaut pourfilter
etreduce
n'importe quoi duitertools
module: 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.map
fonction comme une fonction abstraite pure lors de la programmation fonctionnelle, où vous mappezmap
, ou currymap
, ou autrement bénéficier de parlermap
comme une fonction. Dans Haskell par exemple, une interface de foncteur appeléefmap
gé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ù ilmap(f, *lists)
est raisonnable de faire. L'exemple le plus proche que je peux trouver seraitsumEach = partial(map,sum)
, qui est une doublure qui est à peu près équivalente à:for
boucle : 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,
map
etfilter
et des fonctions similaires (comme leitertools
module 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:
Essayez de faire cela avec une compréhension de la liste:
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:
Vous pouvez essentiellement penser à la
[...]
syntaxe comme passant une expression de générateur au constructeur de liste, commelist(x for x in range(5))
.Bref exemple artificiel
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.ou casser les choses:
Comparaison d'efficacité pour python3
map
est maintenant paresseux: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,
map
en 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'utilisationmap
. 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'ordrex[0], x[1], x[2], ...
.Cependant, disons que nous avons une fonction prédéfinie que
f
nous aimerionsmap
, et nous ignorons la paressemap
en forçant immédiatement l'évaluation aveclist(...)
. Nous obtenons des résultats très intéressants: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(...)
, ellesmap
sont é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
dis
module pour voir si c'est réellement ce qui se passe dans les coulisses:Il semble qu'il vaut mieux utiliser la
[...]
syntaxe quelist(...)
. Malheureusement, lamap
classe est un peu opaque au démontage, mais nous pouvons le faire avec notre test de vitesse.la source
map
etfilter
ainsi que la bibliothèque standarditertools
sont 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 ;-)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 areduce
été déplacé versfunctools
). Personnellement, je ne suis pas d'accord (map
et je suis d'accordfilter
avec les fonctions prédéfinies, en particulier intégrées, ne les utilisez jamais si unlambda
est nécessaire), mais GvR les a fondamentalement appelées non Pythonic depuis des années.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,map
etfilter
je passerais àitertools
(oufunctools
) ou irais entièrement, mais quel que soit le cas, une fois que l'on dit queitertools
c'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".map
/filter
, nonitertools
. La programmation fonctionnelle est parfaitement Pythonique (itertools
,functools
etoperator
ont toutes été conçues spécifiquement avec la programmation fonctionnelle à l'esprit, et j'utilise des idiomes fonctionnels en Python tout le temps), etitertools
fournit des fonctionnalités qui seraient difficiles à mettre en œuvre, c'est spécifiquementmap
etfilter
étant redondant avec des expressions de générateur qui a incité Guido à les détester.itertools
a toujours été bien.Python 2: vous devez utiliser
map
etfilter
au 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:
mais si à la place j'avais dit:
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
x
n'é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
map
etfilter
. Ils empêchent les bogues subtils difficiles à diagnostiquer liés à la portée.Note latérale:
N'oubliez pas d'envisager d'utiliser
imap
etifilter
(initertools
) s'ils conviennent à votre situation!la source
map
et / oufilter
. 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érateurlist(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.En fait,
map
et les compréhensions de liste se comportent très différemment dans le langage Python 3. Jetez un œil au programme Python 3 suivant: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,
squares
elle 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,
map
renvoie une ancienne liste simple, tout comme les compréhensions de liste dans les deux langues. Le point crucial est que la valeur de retour demap
Python 3 (etimap
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èreprint(list(squares))
ligne.Résumer:
la source
map
produire 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 @MnZrKJe 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 unelambda
expression complexe .Il y a aussi une interview quelque part (je ne peux pas la trouver par hasard) où Guido répertorie
lambda
s 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.la source
const
mot - clé en C ++ est un grand triomphe dans ce sens.lambda
, ils ont été rendus si boiteux (pas de déclarations ..) qu'ils sont difficiles à utiliser et limités de toute façon.Voici un cas possible:
contre:
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.
la source
zip
paresseux en utilisantitertools.izip
map(operator.mul, list1, list2)
. C'est sur ces expressions très simples du côté gauche que les compréhensions deviennent maladroites.Si vous envisagez d'écrire du code asynchrone, parallèle ou distribué, vous préférerez probablement
map
une compréhension de liste - car la plupart des packages asynchrones, parallèles ou distribués fournissent unemap
fonction pour surcharger pythonmap
. Ensuite, en transmettant lamap
fonction 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.).la source
Donc, puisque Python 3
map()
est un itérateur, vous devez garder à l'esprit ce dont vous avez besoin: un itérateur ou unlist
objet.Comme @AlexMartelli l'a déjà mentionné ,
map()
est plus rapide que la compréhension de liste uniquement si vous n'utilisez pas delambda
fonction.Je vais vous présenter quelques comparaisons temporelles.
Python 3.5.2 et CPython
J'ai utilisé le bloc - notes Jupiter et en particulier
%timeit
la commande magique intégréeMesures : s == 1000 ms == 1000 * 1000 µs = 1000 * 1000 * 1000 ns
Installer:
Fonction intégrée:
lambda
une fonction: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
Vous avez besoin d'un
list
objet:Utilisez la compréhension de liste si c'est une fonction personnalisée, utilisez
list(map())
s'il y a une fonction intégréeVous n'avez pas besoin d'
list
objet, vous avez juste besoin d'un objet itérable:Utilisez toujours
map()
!la source
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:
J'ai regardé les listes (stockées dans la variable
vals
) des nombres entiers (Pythonint
) et des nombres à virgule flottante (Pythonfloat
) pour augmenter la taille des listes. La classe factice suivanteDummyNum
est considérée:Plus précisément, la
add
mé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.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_comprehension
technique) 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.
la source
map
n'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 donclist(map(methodcaller("add"), vals))
plus rapide que[methodcaller("add")(x) for x in vals]
.map
peut 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 lamethodcaller
surcharge de l'expression ou lambda). Pour ce cas de test spécifique,[*map(DummyNum.add, vals)]
serait plus rapide (carDummyNum.add(x)
etx.add()
aurait essentiellement les mêmes performances).list()
appels explicites sont légèrement plus lents que les compréhensions de liste. Pour une comparaison juste, vous devez écrire[*map(...)]
.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.Je considère que la façon la plus Pythonique est d'utiliser une compréhension de liste au lieu de
map
etfilter
. La raison en est que les listes de compréhension sont plus claires quemap
etfilter
.Comme vous le voyez, une compréhension ne nécessite pas d'
lambda
expressions supplémentaires selon lesmap
besoins. En outre, une compréhension permet également filtrer facilement, tout enmap
exigefilter
pour permettre le filtrage.la source
J'ai essayé le code par @ alex-martelli mais j'ai trouvé des différences
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.
la source
map
renvoie une liste. En Python 3,map
est évalué paresseusement, donc simplement appelermap
ne calcule aucun des nouveaux éléments de la liste, d'où la raison pour laquelle vous obtenez des temps si courts.