Cas
-
Cas commun : Dans la plupart des cas, vous voudrez utiliser une liste de compréhension en python car ce que vous faites sera plus évident pour les programmeurs novices qui lisent votre code. (Ceci ne s'applique pas aux autres langages, où d'autres idiomes peuvent s'appliquer). Ce que vous faites sera même plus évident pour les programmeurs python, puisque les compréhensions de listes sont la norme de facto en python pour l'itération ; elles sont les suivantes attendu .
-
Cas moins fréquent : 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/terse que [sum(x) for x in myLists]
. Vous gagnez l'élégance de ne pas avoir à inventer une variable fictive (par ex. sum(x) for x...
o sum(_) for _...
o sum(readableName) for readableName...
) que vous devez taper deux fois, juste pour itérer. Le même argument vaut pour filter
y reduce
et tout ce qui concerne le itertools
module : si vous avez déjà une fonction à portée de main, vous pouvez aller de l'avant et faire de la programmation fonctionnelle. Cela permet de gagner en lisibilité dans certaines situations, et d'en perdre dans d'autres (par exemple, programmeurs novices, arguments multiples)... mais la lisibilité de votre code dépend fortement de vos commentaires de toute façon.
-
Presque jamais. : Vous pouvez utiliser le map
comme une fonction abstraite pure tout en faisant de la programmation fonctionnelle, où vous mettez en correspondance map
ou le curry map
ou bénéficier d'une discussion sur map
comme une fonction. En Haskell par exemple, une interface de type foncteur appelée fmap
généralise le mappage sur n'importe quelle structure de données. C'est très rare en python parce que 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. (C'est parfois une bonne et parfois une mauvaise chose.) Vous pouvez probablement trouver de rares exemples en python où map(f, *lists)
est une chose raisonnable à faire. L'exemple le plus proche que je puisse trouver est le suivant sumEach = partial(map,sum)
ce qui équivaut à peu près à :
def sumEach(myLists):
return [sum() for in myLists]
-
Il suffit d'utiliser un for
-boucle : Vous pouvez aussi bien sûr simplement 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 habitués à lire le code de cette manière. Les for-loops sont aussi, généralement, les plus efficaces lorsque vous faites simplement une opération complexe qui n'est pas la construction d'une liste comme les comprehensions de liste et map sont optimisées pour (par exemple additionner, 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, à moins d'un rare hoquet pathologique de garbage-collection).
"Pythonisme"
Je n'aime pas le mot "python" car je ne trouve pas que le python soit toujours élégant à mes yeux. Néanmoins, map
y filter
et des fonctions similaires (comme la très utile itertools
) 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 est en fait paresseux en python. Cela signifie que vous pouvez faire ceci (dans python3 ) et votre ordinateur ne manquera pas de mémoire et ne perdra pas toutes vos données non sauvegardées :
>>> map(str, range(10**100))
<map object at 0x2201d50>
Essayez de faire ça avec la compréhension d'une liste :
>>> [str(n) for n in range(10**100)]
# DO NOT TRY THIS AT HOME OR YOU WILL BE SAD #
Notez que les comprehensions de listes sont aussi intrinsèquement paresseuses, mais python a choisi de les implémenter comme non-lazy. . 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>
En gros, vous pouvez penser à la [...]
comme le passage d'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 sont non-lazy, et peuvent donc nécessiter plus de mémoire (à moins que vous n'utilisiez des compréhensions de générateur). Les crochets [...]
rendent souvent les choses évidentes, surtout lorsqu'elles se trouvent dans un fouillis de parenthèses. D'un autre côté, il arrive que l'on finisse par être verbeux, comme en tapant [x for x in...
. Tant que vous gardez vos variables d'itérateurs courtes, les compréhensions de listes sont généralement plus claires si vous n'indentez pas votre code. Mais vous pouvez toujours indenter votre code.
print(
{x:x**2 for x in (-y for y in range(5))}
)
ou de casser les choses :
rangeNeg5 = (-y for y in range(5))
print(
{x:x**2 for x in rangeNeg5}
)
Comparaison de l'efficacité de 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'allez pas utiliser 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 l'emportera sur la surcharge due à l'utilisation de map
. L'inconvénient est que cela est très limité en python par rapport à la plupart des langages fonctionnels : vous ne bénéficiez de cet avantage que si vous accédez à vos données de gauche à droite "dans l'ordre", car les expressions du générateur python ne peuvent être évaluées que dans l'ordre suivant x[0], x[1], x[2], ...
.
Toutefois, disons que nous avons une fonction préétablie 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>)
Les résultats sont de la forme AAA/BBB/CCC, où A a été réalisé sur une station de travail Intel datant de 2010 avec python 3. ?.?, et B et C ont été réalisés sur une station de travail AMD datant de 2013 avec python 3.2.1, avec un matériel extrêmement différent. Le résultat semble être que les compréhensions de cartes et de listes sont comparables en termes de performances, qui sont plus fortement affectées par d'autres facteurs aléatoires. La seule chose que nous pouvons dire semble être que, bizarrement, alors que nous nous attendons à ce que les compréhensions de listes [...]
pour obtenir de meilleurs résultats que les expressions génératrices (...)
, map
est AUSSI plus efficace que les expressions de générateur (encore une fois en supposant que toutes les valeurs sont évaluées/utilisées).
Il est important de réaliser que ces tests supposent une fonction très simple (la fonction d'identité) ; cependant, c'est très bien ainsi car si la fonction était compliquée, le surcoût de performance serait négligeable par rapport aux autres facteurs du programme. (Il peut toujours être intéressant de tester avec d'autres choses simples comme f=lambda x:x+x
)
Si vous savez lire l'assemblage python, vous pouvez utiliser la fonction dis
module pour voir si c'est vraiment ce qui se passe en 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 soit préférable d'utiliser [...]
syntaxe que list(...)
. Malheureusement, le map
est un peu opaque au désassemblage, mais nous pouvons nous contenter de notre test de vitesse.
8 votes
Notez que PyLint vous avertit si vous utilisez la compréhension par carte au lieu de la compréhension par liste, voir message W0141 .
3 votes
@lumbric, je ne suis pas sûr mais cela ne fonctionne que si lambda est utilisé dans map.
2 votes
J'ai réalisé un tutoriel de 17 minutes sur la comparaison entre liste et carte si quelqu'un le trouve utile. youtube.com/watch?v=hNW6Tbp59HQ