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?
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?
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:
$ 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
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 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 filter
et reduce
n'importe quoi du itertools
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 mappez map
, ou curry map
, ou autrement bénéficier de parler map
comme une fonction. Dans Haskell par exemple, une interface de foncteur appelée fmap
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ù 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]
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
et filter
et des fonctions similaires (comme le itertools
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:
>>> 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, 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'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 f
nous aimerions map
, et nous ignorons la paresse 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 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 map
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:
>>> 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 map
classe est un peu opaque au démontage, mais nous pouvons le faire avec notre test de vitesse.
map
et filter
ainsi que la bibliothèque standard itertools
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 a reduce
été déplacé vers functools
). Personnellement, je ne suis pas d'accord ( map
et je suis d'accord filter
avec les fonctions prédéfinies, en particulier intégrées, ne les utilisez jamais si un lambda
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
et filter
je passerais à itertools
(ou functools
) ou irais entièrement, mais quel que soit le cas, une fois que l'on dit que itertools
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
, non itertools
. La programmation fonctionnelle est parfaitement Pythonique ( itertools
, functools
et operator
ont toutes été conçues spécifiquement avec la programmation fonctionnelle à l'esprit, et j'utilise des idiomes fonctionnels en Python tout le temps), et itertools
fournit des fonctionnalités qui seraient difficiles à mettre en œuvre, c'est spécifiquement map
et filter
étant redondant avec des expressions de générateur qui a incité Guido à les détester. itertools
a toujours été bien.
map
et filter
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:
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 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.
Utilisez map
et filter
. Ils empêchent les bogues subtils difficiles à diagnostiquer liés à la portée.
N'oubliez pas d'envisager d'utiliser imap
et ifilter
(in itertools
) s'ils conviennent à votre situation!
map
et / 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.
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:
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, 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 de map
Python 3 (et imap
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:
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 @MnZrK
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 lambda
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.
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:
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.
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 une map
fonction pour surcharger python map
. Ensuite, en transmettant la map
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.).
Donc, puisque Python 3 map()
est un itérateur, vous devez garder à l'esprit ce dont vous avez besoin: un itérateur ou un list
objet.
Comme @AlexMartelli l'a déjà mentionné , map()
est plus rapide que la compréhension de liste uniquement si vous n'utilisez pas de lambda
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é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
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ée
list
objet, vous avez juste besoin d'un objet itérable:Utilisez toujours map()
!
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 DummyNum
est 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 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.
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 donc list(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 la methodcaller
surcharge 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).
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
et filter
. La raison en est que les listes de compréhension sont plus claires 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 d' lambda
expressions supplémentaires selon les map
besoins. En outre, une compréhension permet également filtrer facilement, tout en map
exige filter
pour permettre le filtrage.
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.
map
renvoie une liste. En Python 3, map
est évalué paresseusement, donc simplement appeler map
ne calcule aucun des nouveaux éléments de la liste, d'où la raison pour laquelle vous obtenez des temps si courts.