Question

Y at-il une raison de préférer utiliser map() sur la compréhension de la liste ou vice-versa? Est-ce l'un d'eux généralement plus efficaces ou plus généralement considérés comme pythonique que l'autre?

Était-ce utile?

La solution

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

Un exemple de l'avantage minuscule vitesse de carte lors de l'utilisation de 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 se 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

Autres conseils

Cas

  • cas commun : Presque toujours, vous voulez utiliser une compréhension de liste dans python , car il sera plus évident ce que vous faites pour les programmeurs débutants lire votre code . (Cela ne concerne pas les autres langues, où d'autres idiomes peuvent appliquer.) Il sera encore plus évident ce que vous faites pour les programmeurs Python, puisque la liste sont compréhensions la norme de facto en python pour l'itération; ils sont devraient .
  • cas moins courants : Toutefois, si vous ont déjà une fonction définie , il est souvent raisonnable d'utiliser map, mais il est considéré comme 'unpythonic'. 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 à constituer une variable fictive (par exemple de 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 quoi que ce soit à partir du module itertools: si vous avez déjà une fonction à portée de main, vous pouvez aller de l'avant et faire de la programmation fonctionnelle. Cette lisibilité gagne dans certaines situations, et perd dans d'autres (par exemple, les programmeurs novices, plusieurs arguments) ... mais la lisibilité de votre code dépend fortement de vos commentaires en tout cas.
  • Presque jamais : Vous pouvez utiliser la fonction map en fonction abstraite pur tout en faisant la programmation fonctionnelle, où vous cartographie map, ou taitement map, ou tout autre avantage de parler map comme une fonction. Dans Haskell par exemple, une interface de foncteur appelé fmap généralise la cartographie sur toute structure de données. Ceci est très rare en python, car la grammaire python vous oblige à utiliser de style générateur pour parler d'itération; vous ne pouvez pas 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 peux trouver serait sumEach = partial(map,sum), ce qui est en une ligne qui est très à peu près équivalent à:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • Juste en utilisant une boucle for : Vous pouvez aussi bien sûr il suffit d'utiliser une boucle for. Bien que pas aussi élégant du point de vue programmation fonctionnelle, parfois variables non locales rendent le code plus clair dans les langages de programmation impératifs tels que Python, parce que les gens sont très utilisés pour la lecture de code de cette façon. Pour boucles sont également, en général, le plus efficace lorsque vous faites simplement une opération complexe qui ne construit pas une liste comme liste-compréhensions et la carte sont optimisés pour (par exemple sommateur, ou faire un arbre, etc.) - au moins efficace en termes de mémoire (pas nécessairement en termes de temps, où je me attends au pire un facteur constant, sauf des ordures collecte pathologique rare hoquet).

"pythonisme"

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

Paresse

En termes d'efficacité, comme la plupart des constructions de programmation fonctionnelle, CARTE PEUT ÊTRE LAZY , et en fait est paresseux en python. Cela signifie que vous pouvez le faire (dans python3 ) et votre ordinateur ne sera pas à court de mémoire et de perdre toutes vos données non sauvegardé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 la liste compréhensions sont aussi intrinsèquement paresseux, mais python a choisi de les mettre en œuvre comme non paresseux . Néanmoins, python supporte compréhensions liste paresseux sous la forme de générateur exprSESSIONS, comme suit:

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

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

exemple artificiel bref

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))})

Liste compréhensions ne sont pas paresseux, donc peut nécessiter plus de mémoire (sauf si vous utilisez compréhensions générateur). Les crochets [...] font souvent des choses évidentes, surtout quand dans un désordre de parenthèses. D'autre part, on finit parfois par être bavard comme taper [x for x in.... Tant que vous gardez vos variables iterator courte, la liste compréhensions sont généralement plus claire si vous n'indentez pas votre code. Mais vous pouvez toujours indentez votre code.

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

ou briser les choses:

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

Comparaison de l'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 ne comptez pas utiliser toutes vos données, ou ne savent pas à l'avance combien de données dont vous avez besoin, map dans python3 (et les expressions de générateur python2 ou python3) évitera le calcul de leurs valeurs jusqu'à ce que le dernier moment nécessaire. Habituellement, cela l'emportent généralement sur les frais généraux tout d'utiliser map. L'inconvénient est que cela est très limité en python par opposition aux langues les plus fonctionnelles. Vous obtenez seulement cet avantage si vous accédez à vos données de gauche à droite « pour », parce que les expressions du générateur python ne peut évaluer l'ordre x[0], x[1], x[2], ...

Cependant, disons que nous avons une fonction pré-faites 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é réalisée avec un poste de travail Intel vers 2010 avec Python 3.?.?, Et B et C ont été réalisées avec un vers 2013 station de travail AMD avec Python 3.2 .1, avec du matériel extrêmement différents. Le résultat semble être cette carte et la liste compréhensions des performances comparables, ce qui est le plus fortement affecté par d'autres facteurs aléatoires. La seule chose que nous pouvons dire semble être que, curieusement, alors que nous attendons compréhensions liste [...] de meilleures performances que les expressions du générateur (...), map est également plus efficace que les expressions du générateur (en supposant à nouveau que toutes les valeurs sont évaluées / utilisées).

Il est important de réaliser que ces tests prennent une fonction très simple (la fonction d'identité); mais cela est très bien parce que si la fonction par rapport à d'autres facteurs du programme étaient compliquées, alors les frais généraux de performance serait négligeable. (Il peut encore être intéressant de tester avec d'autres choses simples comme f=lambda x:x+x)

Si vous êtes habile à l'assemblage python de lecture, vous pouvez utiliser le module dis pour voir si cela 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 est préférable d'utiliser la syntaxe [...] que list(...). Malheureusement, la classe map est un peu opaque pour le démontage, mais nous pouvons faire en raison de notre test de vitesse.

Python 2: Vous devez utiliser map et filter au lieu de la liste compréhensions

.

Objectif raison pour laquelle vous devriez les préférer, même si elles ne sont pas « Pythonic » est la suivante:
Ils ont besoin des fonctions / lambdas comme arguments, introduire un nouveau champ .

Je m'y suis 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 au lieu que je lui avais dit:

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

alors tout aurait été très bien.

Vous pouvez dire que j'étais stupide pour utiliser le même nom de variable dans la même portée.

Je n'étais pas. Le code était bien à l'origine - les deux xs ne sont pas dans la même portée
. Ce ne fut qu'après I déplacé le bloc interne à une autre section du code que le problème est venu. (Lire: problème lors de l'entretien, pas de développement), et je ne m'y attendais pas

Oui, si vous ne faites jamais cette erreur puis compréhensions de la liste sont plus élégante.
Mais de son expérience personnelle (et de voir d'autres font la même erreur) Je l'ai vu arriver assez souvent que je pense qu'il est vous ne vaut pas la peine de passer par ces bugs quand se glisser dans votre code.

Conclusion:

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

Side note:

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

En fait, map et liste compréhensions se comportent très différemment dans le langage Python 3. Jetez un coup d'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 il imprime suivi par "[]" "[1, 4, 9]". 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 le Python 2 map langue renvoie une liste ancienne plaine, tout comme la liste compréhensions font 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 itérer sur un iterator contrairement à lorsque vous boucle une liste. Voilà pourquoi squares semble vide dans la dernière ligne de print(list(squares)).

Pour résumer:

  • Lorsque vous traitez avec itérateurs vous devez vous rappeler qu'ils sont stateful et qu'ils mutent que vous les traversent.
  • Les listes sont plus prévisibles, car ils ne changent que lorsque vous les muter explicitement; ils sont conteneurs .
  • Et un bonus: nombres, des chaînes et tuples sont encore plus prévisibles, car ils ne peuvent pas changer du tout; ils sont valeurs .

Je trouve compréhensions de liste sont généralement plus expressifs de ce que je suis en train de faire que map - ils ont tous deux obtenir fait, mais l'ancien sauve la charge mentale d'essayer de comprendre ce qui pourrait être une expression lambda complexe

Il y a aussi une interview là quelque part (je ne peux pas trouver Morisque) où les listes Guido lambdas et les fonctions fonctionnelles comme la chose qu'il a le plus de regrets à accepter en Python, de sorte que vous pourriez faire l'argument selon lequel ils sont ONU- pythonic en vertu de ce.

Voici un cas possible:

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

par rapport à:

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

Je devine que le zip () est une surcharge regrettable et inutile que vous devez livrer si vous insistez sur l'utilisation de la liste au lieu compréhensions de la carte. Si quelqu'un clarifie ce que affirmativement ou négativement.

Si vous envisagez d'écrire une asynchrone, parallèle ou code distribué, vous préférerez probablement map sur une compréhension de liste - comme la plupart asynchrone, parallèle ou paquets distribués fournissent une fonction de map surcharger le map de python. Ensuite, en passant la fonction map appropriée pour le reste de votre code, vous ne pouvez pas avoir à modifier votre numéro de série d'origine pour avoir exécuté en parallèle (etc).

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

, map() est plus rapide que la compréhension de la liste que si vous ne l'utilisez la fonction lambda.

Je vais vous présenter quelques comparaisons de temps.

Python 3.5.2 et CPython
Je l'ai utilisé Jupiter portable et surtout %timeit intégré commande magique
Mesures : s == == 1000 ms 1000 * 1000 ms = 1000 * 1000 * 1000 ns

Configuration:

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

Fonction lambda:

%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, je pensais que ce serait utile d'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 devez objet list:

Utiliser la compréhension de la liste si elle est fonction personnalisée, utilisez list(map()) s'il est fonction builtin

Vous n'avez pas besoin objet list, il vous suffit itérables un:

Utilisez toujours map()!

Je considère que la façon la plus Pythonic est d'utiliser une compréhension de liste au lieu de map et filter. La raison est que la liste compréhensions 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 voyez un, une compréhension ne nécessite pas des expressions lambda supplémentaires que les besoins de map. En outre, une compréhension permet aussi facilement le filtrage, alors que map nécessite filter pour permettre le filtrage.

J'ai couru un test rapide comparant trois méthodes pour appeler la méthode d'un objet. La différence de temps, dans ce cas, est négligeable et est une question de la fonction en question (voir la réponse de Martelli @ Alex) . Ici, je regardais 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]

Je regardais les listes (stockées dans la vals variable) des deux entiers (Python int) et les nombres à virgule flottante (Python de float) pour augmenter la taille de la liste. La DummyNum classe fictive suivante est considérée comme:

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

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

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

Plus précisément, le procédé de add. L'attribut __slots__ est une simple optimisation en Python pour définir la mémoire totale nécessaire par la classe (attributs), ce qui réduit la taille de la mémoire. Voici les parcelles résultant.

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

Consultez cette pastebin pour la source utilisée pour tracer la courbe et les données.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top