Pergunta

Existe uma razão para preferir usar map() sobre compreensão de lista ou vice-versa? É qualquer um deles geralmente mais eficiente ou geralmente considerado mais Python que o outro?

Foi útil?

Solução

map pode ser microscopicamente mais rápido em alguns casos (quando você não está fazendo um lambda para o efeito, mas usando a mesma função no mapa e um listcomp). compreensões lista pode ser mais rápido em outros casos, ea maioria (não todos) pythonistas considerá-los mais directa e mais clara.

Um exemplo da pequena vantagem de velocidade do mapa quando usando exatamente a mesma função:

$ 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

Um exemplo de como comparação de desempenho fica completamente invertida quando mapa precisa um 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

Outras dicas

Bolsas

  • caso comum : Quase sempre, você vai querer usar uma compreensão de lista em python , porque ele vai ser mais óbvio que você está fazendo para programadores iniciantes lendo seu código . (Isto não se aplica a outras línguas, onde outros idiomas podem ser aplicadas.) Ele vai mesmo ser mais óbvio que você está fazendo para programadores Python, desde compreensões lista são o padrão de fato em python para a iteração; eles são esperado .
  • caso menos comum : No entanto, se você já uma função definida , muitas vezes é razoável usar map, que ele é considerado 'unpythonic'. Por exemplo, map(sum, myLists) é mais elegante / conciso do que [sum(x) for x in myLists]. Você ganha a elegância de não ter que fazer-se uma variável dummy (por exemplo sum(x) for x... ou sum(_) for _... ou sum(readableName) for readableName...) que você tem que digitar duas vezes, apenas para iterate. O mesmo argumento vale para filter e reduce e qualquer coisa a partir do módulo itertools: se você já tem uma função útil, você poderia ir em frente e fazer alguma programação funcional. Este ganhos legibilidade em algumas situações, e perde-lo nos outros (por exemplo novatos programadores, múltiplos argumentos) ... mas a legibilidade do código depende, em muito seus comentários de qualquer maneira.
  • Quase nunca : Você pode querer usar a função map como uma função abstrata pura ao fazer programação funcional, onde você está map mapeamento, ou currying map, ou de outra forma beneficiar de falar sobre map como uma função. Em Haskell por exemplo, uma interface functor chamado fmap generaliza mapeamento através de qualquer estrutura de dados. Isto é muito incomum em python, porque a gramática python obriga você a usar o gerador de estilo para falar sobre iteração; você não pode generalizar-lo facilmente. (Isso às vezes é bom e às vezes ruim.) Você pode provavelmente vir acima com exemplos de python raros onde map(f, *lists) é uma coisa razoável a fazer. O exemplo mais próximo que posso chegar a seria sumEach = partial(map,sum), que é um one-liner que é muito mais ou menos equivalente a:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • Apenas usando um for-circuito : Você pode também, naturalmente, apenas usar um loop for. Embora não seja tão elegante do ponto de vista funcional, programação, variáveis, por vezes, não locais tornar o código mais claro em linguagens de programação imperativas, como python, porque as pessoas são muito usados ??para leitura de código dessa forma. Para-loops também são, geralmente, o mais eficiente quando você está apenas fazendo qualquer operação complexa que não está construindo uma lista como lista-compreensões e mapa são otimizados para (por exemplo, soma, ou fazer uma árvore, etc.) - pelo menos eficiente em termos de memória (não necessariamente em termos de tempo, em que eu esperaria na pior das hipóteses um fator constante, salvo algumas raras patológica soluços de coleta de lixo).

"Pythonism"

Eu não gosto da palavra "pythônico" porque eu não achar que pythônico é sempre elegante em meus olhos. No entanto, map e filter e funções similares (como o módulo itertools muito útil) são, provavelmente, considerada unpythonic em termos de estilo.

Preguiça

Em termos de eficiência, como a maioria das construções de programação funcional, MAP pode ser preguiçoso , e de fato é preguiçoso em python. Isso significa que você pode fazer isso (em python3 ) e seu computador não vai ficar sem memória e perder todos os seus dados não salvos:

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

Tente fazer isso com uma compreensão da lista:

>>> [str(n) for n in range(10**100)]
# DO NOT TRY THIS AT HOME OR YOU WILL BE SAD #

Note que compreensões lista também são inerentemente preguiçosos, mas python escolheu para implementá-las como não-preguiçoso . No entanto, python suporta lista preguiçosos compreensões na forma de expr geradoressions, da seguinte forma:

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

Você pode, basicamente, acho que da sintaxe [...] como passar em uma expressão gerador para o construtor lista, como list(x for x in range(5)).

Breve exemplo inventado

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

compreensões de lista são não-preguiçoso, por isso pode exigir mais memória (a menos que você use compreensões gerador). Os colchetes [...] muitas vezes fazem coisas óbvias, especialmente quando em uma confusão de parênteses. Por outro lado, às vezes você acaba sendo detalhado como [x for x in... digitação. Contanto que você mantenha seus iteradoras variáveis ??suma, compreensões lista são geralmente mais clara se você não recuar seu código. Mas você sempre pode recuar seu código.

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

ou quebrar as coisas:

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

comparação Eficiência para python3

map agora é preguiçoso:

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=map(f,xs)'
1000000 loops, best of 3: 0.336 usec per loop            ^^^^^^^^^

Portanto, se você não vai usar todos os seus dados, ou não sabe de antemão a quantidade de dados que você precisa, map em python3 (e gerador de expressões em python2 ou python3) irá evitar calcular seus valores até o último momento necessário. Normalmente, isso geralmente superam qualquer sobrecarga de usar map. A desvantagem é que este é muito limitado em python em oposição à maioria das linguagens funcionais:. Você só tem esse benefício se você acessar seus dados da esquerda para a direita "em ordem", porque gerador de expressões python só pode ser avaliado a x[0], x[1], x[2], ... ordem

No entanto, digamos que temos uma função f pré-fabricados que gostaríamos de map, e ignoramos a preguiça de map forçando imediatamente avaliação com list(...). Nós obter alguns resultados muito interessantes:

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

Em resultados estão na forma AAA / certificação / CCC onde A foi realizada com uma estação de trabalho Intel circa-2010 com python 3.?.? E B e C foram realizados com uma estação de trabalho AMD circa-2013, com python 3.2 .1, com hardware extremamente diferentes. O resultado parece ser que mapa e compreensões lista são comparáveis ??em desempenho, o que é mais fortemente afetada por outros fatores aleatórios. A única coisa que posso dizer parece ser que, estranhamente, enquanto esperamos compreensões lista [...] para um melhor desempenho do que o gerador de expressões (...), map também é mais eficiente que gerador de expressões (novamente, assumindo que todos os valores são avaliados / usadas).

É importante perceber que esses testes assumem uma função muito simples (a função de identidade); no entanto, isso é bom porque se a função foram complicadas, então sobrecarga de desempenho seria insignificante em comparação com outros fatores no programa. (Ela ainda pode ser interessante para teste com outras coisas simples como f=lambda x:x+x)

Se você é hábil em ler python montagem, você pode usar o módulo dis para ver se isso é realmente o que está acontecendo nos bastidores:

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

Parece que é melhor usar a sintaxe [...] que list(...). Infelizmente a classe map é um pouco opaco para a desmontagem, mas podemos fazer devido com o nosso teste de velocidade.

Python 2: Você deve usar map e filter vez de compreensões lista .

Um Objectivo razão por que você deve preferir-los, mesmo que eles não são "Pythonic" é esta:
Eles exigem funções / lambdas como argumentos, que introduzir um novo escopo .

Eu comecei mordido por isso mais de uma vez:

for x, y in somePoints:
    # (several lines of code here)
    squared = [x ** 2 for x in numbers]
    # Oops, x was silently overwritten!

mas se ao invés disso eu tinha dito:

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

então tudo teria sido bom.

Você poderia dizer que eu estava sendo boba para usar o mesmo nome de variável no mesmo escopo.

Eu não estava. O código foi bem originalmente - os dois xs não estavam no mesmo escopo
. Foi só depois que eu foi movida o bloco interno para uma seção diferente do código que o problema surgiu (leia-se: problema durante a manutenção, não desenvolvimento)., E eu não esperava

Sim, se você nunca cometem esse erro , em seguida, compreensões lista são mais elegantes.
Mas, por experiência pessoal (e de ver os outros a fazer o mesmo erro) Eu já vi isso acontecer tantas vezes que eu acho que não vale a pena a dor que você tem que passar quando estes erros fluência em seu código.

Conclusão:

Use map e filter. Eles evitar erros relacionados com o escopo de difícil diagnóstico sutis.

Nota lateral:

Não se esqueça de considerar o uso de imap e ifilter (em itertools) se eles são apropriados para sua situação!

Na verdade, map e compreensões lista se comportam de forma bastante diferente na linguagem Python 3. Dê uma olhada no seguinte programa Python 3:

def square(x):
    return x*x
squares = map(square, [1, 2, 3])
print(list(squares))
print(list(squares))

Você pode esperar-lo para imprimir a linha "[1, 4, 9]" duas vezes, mas em vez disso ele imprime "[1, 4, 9]" seguido de "[]". A primeira vez que você olha squares parece comportar-se como uma sequência de três elementos, mas o segundo tempo como um vazio.

No map linguagem Python 2 retorna uma planície antiga lista, assim como compreensões lista fazer em ambas as línguas. O ponto crucial é que o valor de retorno de map em Python 3 (e imap em Python 2) não é uma lista - que é um iterador

Os elementos são consumidos quando você iterar sobre um iterador ao contrário de quando você iterar sobre uma lista. É por isso que parece squares esvaziar na última linha print(list(squares)).

Para resumir:

  • Ao lidar com iterators você tem que lembrar que eles são stateful e que eles se transformam à medida que atravessa-los.
  • As listas são mais previsível, uma vez que só mudam quando você transformar-los explicitamente; eles são recipientes .
  • E um bônus: números, strings e tuplas são ainda mais previsível, uma vez que não pode mudar em tudo; eles são valores .

Eu acho compreensões lista são geralmente mais expressivo do que eu estou tentando fazer do que map -. Ambos fazê-lo, mas o ex-salva a carga mental de tentar compreender o que poderia ser uma expressão lambda complexo

Há também uma entrevista lá fora em algum lugar (eu não posso encontrá-lo sem constrangimento), onde Guido listas lambdas e as funções funcionais como a coisa que ele mais se arrepende de aceitar em Python, assim que você poderia fazer o argumento de que eles são un- Pythonic em virtude disso.

Aqui está um caso possível:

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

contra:

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

Eu estou supondo que o zip () é uma sobrecarga infeliz e desnecessário que você precisa para entrar em se você insistir em usar compreensões lista em vez do mapa. Seria ótimo se alguém esclarece este se afirmativamente ou negativamente.

Se você está pensando em escrever qualquer assíncrona, paralelo ou código distribuído, provavelmente você vai preferir map sobre uma compreensão da lista - como a maioria assíncrona, paralelo ou pacotes distribuídos fornecer uma função map para map sobrecarga de python. Em seguida, passando a função map apropriado para o resto do seu código, você não pode ter que modificar seu código de série original para que seja executado em paralelo (etc).

Assim, desde Python 3, map() é um iterador, você necessidade de manter em mente o que você precisa:. um objeto iterador ou list

Como @AlexMartelli já mencionado , map() é mais rápido do que compreensão da lista somente se você não usar a função lambda.

Vou apresentar-lhe algumas comparações de tempo.

Python 3.5.2 e CPython
Eu usei Jupiter notebook e, especialmente, %timeit comando interno mágica
Medidas : s == 1000 ms == 1000 * 1000 mS = 1000 * 1000 * 1000 ns

Configuração:

x_list = [(i, i+1, i+2, i*2, i-9) for i in range(1000)]
i_list = list(range(1000))

Built-in função:

%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

função 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

Há também tal coisa como gerador de expressão, consulte PEP- 0289 . Então eu pensei que seria útil para adicioná-lo à comparação

%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

Você precisa list objeto:

Use compreensão da lista se for função personalizada, uso list(map()) se houver builtin função

Você não precisa objeto list, você só precisa de um iterable:

Sempre uso map()!

Eu considero que a maneira mais Pythonic é usar uma compreensão de lista em vez de map e filter. A razão é que compreensões lista são mais claras do que map e 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

Como você um ver, uma compreensão não requer expressões lambda extras como necessidades map. Além disso, a compreensão também permite filtrar facilmente, enquanto map requer filter para permitir filtragem.

Eu corri um teste rápido comparando três métodos para chamar o método de um objeto. A diferença de tempo, neste caso, é insignificante e é uma questão da função em questão (ver do @Alex Martelli ) . Aqui, eu olhei para os seguintes métodos:

# 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]

Eu olhei para listas (armazenados na variável vals) dos dois inteiros (Python int) e números de ponto flutuante (Python float) para aumento do tamanho da lista. A seguir DummyNum classe fictícia é considerada:

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

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

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

Especificamente, o método add. O atributo __slots__ é uma optimização simples em Python para definir a memória total necessário pela classe (atributos), reduzindo o tamanho da memória. Aqui estão as parcelas resultantes.

Desempenho de métodos de objeto mapeamento Python

Como foi referido anteriormente, a técnica utilizada faz uma diferença mínima e você deve código de uma maneira que é mais legível para você, ou na circunstância particular. Neste caso, a compreensão da lista (técnica map_comprehension) é mais rápido para ambos os tipos de adições em um objeto, especialmente com listas mais curtas.

Visite este pastebin para a fonte usada para gerar o enredo e os dados.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top