Compreensão da lista vs mapa

733

Existe um motivo para preferir usar o map()excesso de compreensão da lista ou vice-versa? Um deles é geralmente mais eficiente ou considerado geralmente mais pitônico que o outro?

TimothyAWiseman
fonte
8
Observe que o PyLint avisa se você usar o mapa em vez da compreensão da lista, consulte a mensagem W0141 .
Lumbric 31/10/2013
2
@ Lumbric, não tenho certeza, mas isso acontece apenas se lambda é usado no mapa.
0xc0de 23/05

Respostas:

662

mappode ser microscopicamente mais rápido em alguns casos (quando você NÃO está criando um lambda para esse fim, mas usando a mesma função no mapa e em um listcomp). Em outros casos, a compreensão da lista pode ser mais rápida e a maioria (não todos) os pythonistas os consideram mais diretos e claros.

Um exemplo da pequena vantagem de velocidade do mapa ao usar 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 a comparação de desempenho é completamente revertida quando o mapa precisa de 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
Alex Martelli
fonte
39
Sim, de fato, nosso guia interno de estilo Python no trabalho recomenda explicitamente os listcomps contra o mapa e o filtro (nem mesmo mencionando o pequeno, mas mensurável, mapa de melhoria de desempenho pode dar em alguns casos ;-).
Alex Martelli
46
Não quero comentar sobre os infinitos pontos de estilo de Alex, mas às vezes o mapa parece mais fácil de ler para mim: data = map (str, some_list_of_objects). Alguns dos mais outra ... operator.attrgetter, operator.itemgetter, etc.
Gregg Lind
57
map(operator.attrgetter('foo'), objs)mais fácil de ler do que [o.foo for o in objs]?!
Alex Martelli
52
@ Alex: Prefiro não introduzir nomes desnecessários, como oaqui, e seus exemplos mostram o porquê.
Reid Barton
29
Eu acho que o @GreggLind tem razão, com o str()exemplo dele .
Eric O Lebigot 5/10
474

Estojos

  • Caso comum : quase sempre, você desejará usar uma compreensão de lista em python, porque será mais óbvio o que você está fazendo para programadores iniciantes que leem seu código. (Isso não se aplica a outras linguagens, onde outras expressões idiomáticas podem ser aplicadas.) Será ainda mais óbvio o que você está fazendo com os programadores de python, pois as compreensões de lista são o padrão de fato no python para iteração; eles são esperados .
  • Caso menos comum : no entanto, se você já tem uma função definida , geralmente é razoável usá-la map, embora seja considerada 'não-tônica'. Por exemplo, map(sum, myLists)é mais elegante / conciso que [sum(x) for x in myLists]. Você ganha a elegância de não precisar criar uma variável fictícia (por exemplo, sum(x) for x...ou sum(_) for _...ou sum(readableName) for readableName...) que você deve digitar duas vezes, apenas para iterar. O mesmo argumento vale para filtere reducee nada do itertoolsmódulo: se você já tem uma função útil, você poderia ir em frente e fazer alguma programação funcional. Isso aumenta a legibilidade em algumas situações e a perde em outras (por exemplo, programadores iniciantes, vários argumentos) ... mas a legibilidade do seu código depende muito dos seus comentários.
  • Quase nunca : você pode usar a mapfunção como uma função abstrata pura enquanto faz a programação funcional, onde está mapeando mapou fazendo curry map, ou se beneficiando de falar mapcomo uma função. Em Haskell, por exemplo, uma interface de functor chamada fmapgeneraliza o mapeamento sobre qualquer estrutura de dados. Isso é muito incomum no python porque a gramática do python obriga a usar o estilo de gerador para falar sobre iteração; você não pode generalizar facilmente. (Isso às vezes é bom e às vezes ruim.) Você provavelmente pode criar exemplos raros de python, onde map(f, *lists)é uma coisa razoável a se fazer. O exemplo mais próximo que eu posso sugerir seria sumEach = partial(map,sum), que é uma linha que é aproximadamente equivalente a:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • Apenas usando um forloop : Você também pode, é claro, usar um loop for. Embora não sejam tão elegantes do ponto de vista da programação funcional, às vezes variáveis ​​não locais tornam o código mais claro em linguagens de programação imperativas, como python, porque as pessoas estão muito acostumadas a ler código dessa maneira. Os loops for também são, geralmente, os mais eficientes quando você está apenas realizando uma operação complexa que não está construindo uma lista, como compreensão de lista e mapa, que são otimizados (por exemplo, somar ou criar uma árvore etc.) - pelo menos eficiente em termos de memória (não necessariamente em termos de tempo, onde eu esperaria, na pior das hipóteses, um fator constante, exceto alguns raros soluços patológicos na coleta de lixo).

"Pitthonismo"

Não gosto da palavra "pitônico" porque não acho que o pitônico seja sempre elegante aos meus olhos. No entanto, mape filterfunções similares (como o itertoolsmódulo muito útil ) provavelmente são consideradas não-sintônicas em termos de estilo.

Preguiça

Em termos de eficiência, como a maioria das construções de programação funcional, o MAP CAN BE LAZY e de fato é preguiçoso em python. Isso significa que você pode fazer isso (em python3 ) e o computador não ficará sem memória e perderá todos os 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 #

Observe que as compreensões de lista também são inerentemente preguiçosas, mas o python optou por implementá-las como não preguiçosas . No entanto, o python suporta compreensões de lista lenta na forma de expressões geradoras, da seguinte maneira:

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

Você pode basicamente pensar na [...]sintaxe como passando uma expressão geradora para o construtor da lista, como list(x for x in range(5)).

Breve exemplo artificial

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

As compreensões de lista não são preguiçosas, portanto, pode exigir mais memória (a menos que você use compreensões de gerador). Os colchetes [...]geralmente tornam as coisas óbvias, especialmente quando estão entre parênteses. Por outro lado, às vezes você acaba sendo verboso como digitar [x for x in.... Contanto que você mantenha as variáveis ​​do iterador curtas, a compreensão da lista geralmente será mais clara se você não recuar o código. Mas você sempre pode recuar seu código.

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

ou acabar com as coisas:

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

Comparação de 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 usará todos os seus dados ou não souber antecipadamente quantos dados precisa, mapem python3 (e expressões geradoras em python2 ou python3) evitará o cálculo de seus valores até o último momento necessário. Geralmente, isso geralmente supera qualquer sobrecarga de uso map. A desvantagem é que isso é muito limitado no python, em oposição à maioria das linguagens funcionais: você só obtém esse benefício se acessar os dados da esquerda para a direita "em ordem", porque as expressões do gerador python só podem ser avaliadas na ordem x[0], x[1], x[2], ....

No entanto, digamos que temos uma função pré-criada fque gostaríamos de map, e ignoramos a preguiça de mapforçar imediatamente a avaliação list(...). Temos 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>)

Os resultados estão no formato AAA / BBB / CCC, onde A foi realizado em uma estação de trabalho Intel por volta de 2010 com python 3.?.? E B e C foram executados em uma estação de trabalho AMD por volta de 2013 com python 3.2.1, com hardware extremamente diferente. O resultado parece ser que as compreensões de mapas e listas são comparáveis ​​em desempenho, o que é mais fortemente afetado por outros fatores aleatórios. A única coisa que podemos dizer parece ser que, estranhamente, enquanto esperamos que as compreensões de lista tenham um [...]desempenho melhor que as expressões geradoras (...), maptambém é mais eficiente que as expressões geradoras (novamente assumindo que todos os valores são avaliados / usados).

É 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 fosse complicada, a sobrecarga de desempenho seria insignificante em comparação com outros fatores no programa. (Ainda pode ser interessante testar com outras coisas simples, como f=lambda x:x+x)

Se você é habilidoso em ler assembly python, pode usar o dismódulo para ver se é isso 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 [...]sintaxe do que list(...). Infelizmente, a mapclasse é um pouco opaca para desmontar, mas podemos fazer o devido com nosso teste de velocidade.

ninjagecko
fonte
5
"o muito útil módulo de ferramentas [é] provavelmente considerado não-tônico em termos de estilo". Hmm. Também não gosto do termo "Pythonic", então, em certo sentido, não me importo com o que isso significa, mas não acho justo para quem o usa dizer que, de acordo com os construtos "Pythonicness" mape filterjuntamente com a biblioteca padrão, itertoolssão inerentemente de estilo ruim. A menos que GvR realmente diz que eles eram ou um erro terrível ou unicamente para o desempenho, a conclusão natural se é isso que "Pythonicness" diz é esquecê-lo tão estúpido ;-)
Steve Jessop
4
@SteveJessop: Na verdade, Guido achou que largar map/ filterera uma ótima idéia para o Python 3 , e apenas uma rebelião de outros pythonistas os manteve no espaço de nome interno (enquanto reducefoi movido para functools). Pessoalmente, não concordo ( mape estou filterbem com funções predefinidas, especialmente embutidas, nunca as use se lambdafor necessário), mas o GvR basicamente as chamou de Pythonic por anos.
ShadowRanger
@ ShadowRanger: verdade, mas o GvR planejava remover itertools? A parte que cito dessa resposta é a principal alegação que me confunde. Eu não sei se ele está no mundo ideal dele mape filterse mudaria para itertools(ou functools) ou iria completamente, mas qualquer que seja o caso, uma vez que se diga que itertoolsé não-Pitônico na sua totalidade, então realmente não sei o que é "Pitônico". deveria significar, mas não acho que possa ser algo parecido com "o que o GvR recomenda que as pessoas usem".
Steve Jessop
2
@ SteveJessop: Eu estava apenas abordando map/ filter, não itertools. A programação funcional é perfeitamente Pythonic ( itertools, functoolse operatorforam todos projetados especificamente com programação funcional em mente, e eu uso expressões funcionais em Python o tempo todo), e itertoolsfornece recursos que seria uma dor de implementar si mesmo, é especificamente mape filterser redundante com gerador de expressões isso fez Guido odiá-los. itertoolssempre foi bom.
ShadowRanger
1
Eu poderia favorito essa resposta se houvesse uma maneira. Bem explicado.
NelsonGon
95

Python 2: você deve usar mape filternão a compreensão da lista.

Uma razão objetiva pela qual você deve preferi-los, mesmo que não sejam "pitonicos", é o seguinte:
eles requerem funções / lambdas como argumentos, o que introduz um novo escopo .

Fui 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 eu tivesse dito:

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

então tudo ficaria bem.

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

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

Sim, se você nunca cometer esse erro , a compreensão da lista será mais elegante.
Mas, por experiência pessoal (e ao ver os outros cometerem o mesmo erro), já vi isso acontecer várias vezes e acho que não vale a pena a dor que você passa quando esses bugs entram no seu código.

Conclusão:

Use mape filter. Eles evitam erros sutis, difíceis de diagnosticar, relacionados ao escopo.

Nota:

Não se esqueça de considerar o uso imape ifilter(in itertools) se eles forem apropriados para sua situação!

user541686
fonte
7
Obrigado por apontar isso. Não me ocorreu explicitamente que a compreensão da lista estava no mesmo escopo e poderia ser um problema. Com isso dito, acho que algumas das outras respostas deixam claro que a compreensão da lista deve ser a abordagem padrão na maioria das vezes, mas que isso é algo a ser lembrado. Esse também é um bom lembrete geral para manter as funções (e, portanto, o escopo) pequenas, realizar testes de unidade completos e usar declarações de asserção.
TimothyAWiseman
13
@ wim: Tratava-se apenas do Python 2, embora se aplique ao Python 3 se você quiser permanecer compatível com versões anteriores. Eu sabia disso e já usava o Python há um tempo (sim, mais do que apenas alguns meses), e ainda assim aconteceu comigo. Eu já vi outros que são mais espertos do que eu, caem na mesma armadilha. Se você é tão inteligente e / ou experiente que isso não é um problema para você, então estou feliz por você, não acho que a maioria das pessoas seja como você. Se fosse, não haveria tanta necessidade de corrigi-lo no Python 3. #
user541686
12
Sinto muito, mas você escreveu isso no final de 2012, bem depois que o python 3 está em cena, e a resposta é como se você estivesse recomendando um estilo impopular de codificação em python, só porque você foi mordido por um bug ao cortar e cortar colando código. Nunca afirmei ser inteligente ou experiente, apenas não concordo que a afirmação ousada seja justificada por seus motivos.
Wim
8
@wim: Hein? O Python 2 ainda é usado em muitos lugares, o fato de o Python 3 existir não muda isso. E quando você diz "não é exatamente um bug sutil para quem já usou o Python por mais de alguns meses", essa frase literalmente significa "isso só diz respeito a desenvolvedores inexperientes" (claramente você não). E, para que conste, você claramente não leu a resposta porque eu disse em negrito que estava movendo , não copiando o código. Os erros de copiar e colar são bastante uniformes nos idiomas. Esse tipo de bug é mais exclusivo do Python devido ao seu escopo; é mais sutil e fácil de esquecer e perder.
user541686
3
Ainda não é um motivo lógico para mudar para mape / ou filter. De qualquer forma, a tradução mais direta e lógica para evitar seu problema não é, map(lambda x: x ** 2, numbers)mas sim uma expressão geradora list(x ** 2 for x in numbers)que não vaza, como JeromeJ já apontou. Olhe Mehrdad, não tome uma votação tão pessoal, apenas discordo totalmente do seu raciocínio aqui.
Wim
46

Na verdade, as mapcompreensões de lista se comportam de maneira 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 que imprima a linha "[1, 4, 9]" duas vezes, mas, em vez disso, imprime "[1, 4, 9]" seguido de "[]". A primeira vez que você olha squaresparece se comportar como uma sequência de três elementos, mas a segunda vez como um vazio.

Na linguagem Python 2, mapretorna uma lista antiga simples, assim como as compreensões de lista nos dois idiomas. O ponto crucial é que o valor de retorno mapno Python 3 (e imapno Python 2) não é uma lista - é um iterador!

Os elementos são consumidos quando você itera sobre um iterador, diferente da iteração sobre uma lista. É por isso que squaresparece vazio na última print(list(squares))linha.

Para resumir:

  • Ao lidar com iteradores, lembre-se de que eles são stateful e que sofrem mutação à medida que você os percorre.
  • As listas são mais previsíveis, pois só mudam quando você as modifica explicitamente; eles são recipientes .
  • E um bônus: números, strings e tuplas são ainda mais previsíveis, pois não podem mudar; eles são valores .
raek
fonte
esse é provavelmente o melhor argumento para a compreensão de listas. O mapa de pythons não é o mapa funcional, mas o enteado ruivo de uma implementação funcional. Muito triste, porque eu realmente não gosto de compreensões.
Semiomant 29/06
@semiomant Eu diria que o mapa lento (como em python3) é mais 'funcional' do que o mapa ansioso (como em python2). Por exemplo, o mapa em Haskell é preguiçoso (bem, tudo em Haskell é preguiçoso ...). De qualquer forma, o mapa lento é melhor para encadear mapas - se você tem um mapa aplicado ao mapa aplicado ao mapa, há uma lista para cada chamada intermediária do mapa no python2, enquanto no python3 você tem apenas uma lista resultante, portanto, é mais eficiente em termos de memória .
MnZrK 21/10
Eu acho que o que eu quero é mapproduzir uma estrutura de dados, não um iterador. Mas talvez os iteradores preguiçosos sejam mais fáceis do que as estruturas de dados preguiçosas. Alimento para o pensamento. Obrigado @MnZrK
semiomant 24/10
Você quer dizer que o mapa retorna um iterável, não um iterador.
user541686
16

Acho que as compreensões de lista são geralmente mais expressivas do que estou tentando fazer do que map- elas fazem isso, mas a primeira economiza a carga mental de tentar entender o que poderia ser uma lambdaexpressão complexa .

Também há uma entrevista em algum lugar (não consigo encontrar de imediato) onde Guido lista se lambdae as funções funcionais como a coisa que ele mais se arrepende de aceitar em Python, para que você possa argumentar que eles não são pitonistas em virtude por essa.

Dan
fonte
9
Sim, suspiro, mas a intenção original de Guido de remover o lambda completamente no Python 3 recebeu uma série de pressões contra ele, então ele voltou apesar do meu forte apoio - ah, bem, acho que o lambda é muito útil em muitos casos SIMPLES , o único O problema é quando excede os limites de SIMPLE ou é atribuído a um nome (nesse último caso, é uma duplicata boba e deficiente de def! -).
Alex Martelli
1
A entrevista em que você está pensando é a seguinte: amk.ca/python/writing/gvr-interview , onde Guido diz "Às vezes, fui muito rápido em aceitar contribuições e depois percebi que era um erro. Um exemplo seria alguns dos recursos de programação funcional, como funções lambda, lambda é uma palavra-chave que permite criar uma pequena função anônima; funções internas, como mapear, filtrar e reduzir, executam uma função em um tipo de sequência, como uma lista. "
31411 J. Taylor
3
@ Alex, eu não tenho seus anos de experiência, mas já vi muito mais compreensão de lista do que lambdas. Obviamente, abusar dos recursos da linguagem é sempre uma tentação difícil de resistir. É interessante que as compreensões da lista (empiricamente) pareçam mais propensas a abuso do que as lambdas, embora não tenha certeza do porquê. Também vou apontar que "manco" nem sempre é uma coisa ruim. Reduzir o escopo de "coisas que esta linha pode estar fazendo" às vezes pode facilitar o leitor. Por exemplo, a constpalavra - chave em C ++ é um grande triunfo nesse sentido.
Stuart Berg
> guido. Essa é outra evidência de que Guido está louco. É claro lambdaque foram feitos tão coxos (sem declarações ...) que são difíceis de usar e limitados de qualquer maneira.
Javadba
16

Aqui está um caso possível:

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

versus:

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

Eu acho que o zip () é uma sobrecarga infeliz e desnecessária que você precisa se dedicar se insistir em usar a compreensão da lista em vez do mapa. Seria ótimo se alguém esclarecesse isso afirmativamente ou negativamente.

Andz
fonte
"[op1 * op2 de op1, op2 em zip (lista1, lista2)]" | s / form / for / E uma lista equivalente sem zip: (menos legível) [list1 [i] * list2 [i] para i no intervalo (len (list1))]]
frágil
2
Deveria ser "para" não "de" na sua segunda citação de código, @andz, e também no comentário de @ fraco. Eu pensei que tinha descoberto uma nova abordagem sintática para listar compreensões ... Droga.
physicsmichael
4
a adicionar um comentário muito tarde, você pode fazer zippreguiçoso usandoitertools.izip
tacaswell
5
Eu acho que ainda prefiro map(operator.mul, list1, list2). É nessas expressões simples do lado esquerdo que as compreensões se tornam desajeitadas.
Yann Vernier
1
Eu não tinha percebido que o mapa poderia levar várias iterables como entradas para sua função e, assim, evitar um zip.
bli
16

Se você planeja escrever qualquer código assíncrono, paralelo ou distribuído, provavelmente preferirá mapa compreensão de uma lista - já que a maioria dos pacotes assíncronos, paralelos ou distribuídos fornece uma mapfunção para sobrecarregar os python map. Em seguida, passando a mapfunção apropriada para o restante do seu código, talvez você não precise modificar o código de série original para que ele seja executado em paralelo (etc).

Mike McKerns
fonte
9

Portanto, como o Python 3 map()é um iterador, você precisa ter em mente o que precisa: um iterador ou listobjeto.

Como o @AlexMartelli já mencionou , map()é mais rápido que a compreensão da lista apenas se você não usar a lambdafunção.

Vou apresentar algumas comparações de tempo.

Python 3.5.2 e CPython
Eu usei o notebook Jupiter e, especialmente, o %timeitcomando mágico interno
Medidas : s == 1000 ms == 1000 * 1000 µs = 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))

Função incorporada:

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

%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

Também existe expressão de gerador, consulte PEP-0289 . Então eu pensei que seria útil 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 de um listobjeto:

Use a compreensão da lista se for uma função personalizada, use list(map())se houver uma função interna

Você não precisa de um listobjeto, apenas um iterável:

Sempre use map()!

vishes_shell
fonte
1

Fiz um teste rápido comparando três métodos para invocar o método de um objeto. A diferença de horário, neste caso, é insignificante e é uma questão da função em questão (consulte a resposta de @Alex Martelli ). Aqui, 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 (armazenadas na variável vals) de números inteiros (Python int) e números de ponto flutuante (Python float) para aumentar o tamanho da lista. A seguinte classe fictícia DummyNumé considerada:

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

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

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

Especificamente, o addmétodo O __slots__atributo é uma otimização simples no Python para definir a memória total necessária à classe (atributos), reduzindo o tamanho da memória. Aqui estão os gráficos resultantes.

Desempenho do mapeamento de métodos de objeto Python

Como declarado anteriormente, a técnica usada faz uma diferença mínima e você deve codificar da maneira que for mais legível para você ou na circunstância específica. Nesse caso, a compreensão da lista ( map_comprehensiontécnica) é mais rápida para os dois tipos de adições em um objeto, especialmente em listas mais curtas.

Visite este pastebin para a fonte usada para gerar o gráfico e os dados.

craymichael
fonte
1
Como já explicado em outras respostas, mapé mais rápido apenas se a função for chamada exatamente da mesma maneira (ou seja, [*map(f, vals)]vs. [f(x) for x in vals]). Então list(map(methodcaller("add"), vals))é mais rápido que [methodcaller("add")(x) for x in vals]. mappode não ser mais rápido quando a contraparte em loop usa um método de chamada diferente que pode evitar sobrecarga (por exemplo, x.add()evita a methodcallersobrecarga da expressão ou lambda). Para este caso de teste específico, [*map(DummyNum.add, vals)]seria mais rápido (porque DummyNum.add(x)e x.add()tem basicamente o mesmo desempenho).
GZ0 31/07/19
1
A propósito, list()chamadas explícitas são um pouco mais lentas que as compreensões de lista. Para uma comparação justa, você precisa escrever [*map(...)].
GZ0 31/07/19
@ GZ0 obrigado pelo ótimo feedback! Tudo faz sentido, e eu não sabia que as list()chamadas aumentavam a sobrecarga. Deveria ter passado mais tempo lendo as respostas. Vou repetir esses testes para uma comparação justa, por mais insignificantes que sejam as diferenças.
Craymichael 31/07/19
0

Considero que a maneira mais pitônica é usar uma compreensão de lista em vez de mape filter. A razão é que as compreensões da lista são mais claras que mape 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ê vê, uma compreensão não requer lambdaexpressões extras conforme as mapnecessidades. Além disso, uma compreensão também permite filtrar facilmente, enquanto maprequer filterpermitir a filtragem.

lmiguelvargasf
fonte
0

Eu tentei o código por @ alex-martelli, mas encontrei algumas discrepâncias

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

O mapa leva a mesma quantidade de tempo, mesmo para intervalos muito grandes, enquanto o uso da compreensão da lista leva muito tempo, como é evidente no meu código. Portanto, além de ser considerado "antitônico", não enfrentei nenhum problema de desempenho relacionado ao uso do mapa.

Mohit Raj
fonte
3
Esta é uma pergunta muito antiga, e a resposta a que você está se referindo provavelmente foi escrita em referência ao Python 2, onde mapretorna uma lista. No Python 3, mapé avaliado preguiçosamente; portanto, simplesmente chamar mapnão computa nenhum dos novos elementos da lista, portanto, por que você obtém tempos tão curtos.
kaya3
Eu acho que você está usando o Python 3.x Quando fiz essa pergunta, o Python 3 havia sido lançado recentemente e o Python 2.x era o padrão.
TimothyAWiseman