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?
python
list-comprehension
map-function
TimothyAWiseman
fonte
fonte
Respostas:
map
pode 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:
Um exemplo de como a comparação de desempenho é completamente revertida quando o mapa precisa de um lambda:
fonte
map(operator.attrgetter('foo'), objs)
mais fácil de ler do que[o.foo for o in objs]
?!o
aqui, e seus exemplos mostram o porquê.str()
exemplo dele .Estojos
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...
ousum(_) for _...
ousum(readableName) for readableName...
) que você deve digitar duas vezes, apenas para iterar. O mesmo argumento vale parafilter
ereduce
e nada doitertools
mó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.map
função como uma função abstrata pura enquanto faz a programação funcional, onde está mapeandomap
ou fazendo currymap
, ou se beneficiando de falarmap
como uma função. Em Haskell, por exemplo, uma interface de functor chamadafmap
generaliza 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, ondemap(f, *lists)
é uma coisa razoável a se fazer. O exemplo mais próximo que eu posso sugerir seriasumEach = partial(map,sum)
, que é uma linha que é aproximadamente equivalente a:for
loop : 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,
map
efilter
funções similares (como oitertools
mó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:
Tente fazer isso com uma compreensão da lista:
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:
Você pode basicamente pensar na
[...]
sintaxe como passando uma expressão geradora para o construtor da lista, comolist(x for x in range(5))
.Breve exemplo artificial
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.ou acabar com as coisas:
Comparação de eficiência para python3
map
agora é preguiçoso:Portanto, se você não usará todos os seus dados ou não souber antecipadamente quantos dados precisa,
map
em 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 usomap
. 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 ordemx[0], x[1], x[2], ...
.No entanto, digamos que temos uma função pré-criada
f
que gostaríamos demap
, e ignoramos a preguiça demap
forçar imediatamente a avaliaçãolist(...)
. Temos alguns resultados muito interessantes: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(...)
,map
també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
dis
módulo para ver se é isso o que está acontecendo nos bastidores:Parece que é melhor usar
[...]
sintaxe do quelist(...)
. Infelizmente, amap
classe é um pouco opaca para desmontar, mas podemos fazer o devido com nosso teste de velocidade.fonte
map
efilter
juntamente com a biblioteca padrão,itertools
sã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 ;-)map
/filter
era uma ótima idéia para o Python 3 , e apenas uma rebelião de outros pythonistas os manteve no espaço de nome interno (enquantoreduce
foi movido parafunctools
). Pessoalmente, não concordo (map
e estoufilter
bem com funções predefinidas, especialmente embutidas, nunca as use selambda
for necessário), mas o GvR basicamente as chamou de Pythonic por anos.itertools
? A parte que cito dessa resposta é a principal alegação que me confunde. Eu não sei se ele está no mundo ideal delemap
efilter
se mudaria paraitertools
(oufunctools
) ou iria completamente, mas qualquer que seja o caso, uma vez que se diga queitertools
é 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".map
/filter
, nãoitertools
. A programação funcional é perfeitamente Pythonic (itertools
,functools
eoperator
foram todos projetados especificamente com programação funcional em mente, e eu uso expressões funcionais em Python o tempo todo), eitertools
fornece recursos que seria uma dor de implementar si mesmo, é especificamentemap
efilter
ser redundante com gerador de expressões isso fez Guido odiá-los.itertools
sempre foi bom.Python 2: você deve usar
map
efilter
nã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:
mas se eu tivesse dito:
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
x
s 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
map
efilter
. Eles evitam erros sutis, difíceis de diagnosticar, relacionados ao escopo.Nota:
Não se esqueça de considerar o uso
imap
eifilter
(initertools
) se eles forem apropriados para sua situação!fonte
map
e / oufilter
. 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 geradoralist(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.Na verdade, as
map
compreensões de lista se comportam de maneira bastante diferente na linguagem Python 3. Dê uma olhada no seguinte programa Python 3: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
squares
parece se comportar como uma sequência de três elementos, mas a segunda vez como um vazio.Na linguagem Python 2,
map
retorna uma lista antiga simples, assim como as compreensões de lista nos dois idiomas. O ponto crucial é que o valor de retornomap
no Python 3 (eimap
no 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
squares
parece vazio na últimaprint(list(squares))
linha.Para resumir:
fonte
map
produzir 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 @MnZrKAcho 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 umalambda
expressão complexa .Também há uma entrevista em algum lugar (não consigo encontrar de imediato) onde Guido lista se
lambda
e 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.fonte
const
palavra - chave em C ++ é um grande triunfo nesse sentido.lambda
que foram feitos tão coxos (sem declarações ...) que são difíceis de usar e limitados de qualquer maneira.Aqui está um caso possível:
versus:
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.
fonte
zip
preguiçoso usandoitertools.izip
map(operator.mul, list1, list2)
. É nessas expressões simples do lado esquerdo que as compreensões se tornam desajeitadas.Se você planeja escrever qualquer código assíncrono, paralelo ou distribuído, provavelmente preferirá
map
a compreensão de uma lista - já que a maioria dos pacotes assíncronos, paralelos ou distribuídos fornece umamap
função para sobrecarregar os pythonmap
. Em seguida, passando amap
funçã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).fonte
Portanto, como o Python 3
map()
é um iterador, você precisa ter em mente o que precisa: um iterador oulist
objeto.Como o @AlexMartelli já mencionou ,
map()
é mais rápido que a compreensão da lista apenas se você não usar alambda
função.Vou apresentar algumas comparações de tempo.
Python 3.5.2 e CPython
Eu usei o notebook Jupiter e, especialmente, o
%timeit
comando mágico internoMedidas : s == 1000 ms == 1000 * 1000 µs = 1000 * 1000 * 1000 ns
Configuração:
Função incorporada:
lambda
função:Também existe expressão de gerador, consulte PEP-0289 . Então eu pensei que seria útil adicioná-lo à comparação
Você precisa de um
list
objeto:Use a compreensão da lista se for uma função personalizada, use
list(map())
se houver uma função internaVocê não precisa de um
list
objeto, apenas um iterável:Sempre use
map()
!fonte
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:
Eu olhei para listas (armazenadas na variável
vals
) de números inteiros (Pythonint
) e números de ponto flutuante (Pythonfloat
) para aumentar o tamanho da lista. A seguinte classe fictíciaDummyNum
é considerada:Especificamente, o
add
mé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.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_comprehension
té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.
fonte
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ãolist(map(methodcaller("add"), vals))
é mais rápido que[methodcaller("add")(x) for x in vals]
.map
pode 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 amethodcaller
sobrecarga da expressão ou lambda). Para este caso de teste específico,[*map(DummyNum.add, vals)]
seria mais rápido (porqueDummyNum.add(x)
ex.add()
tem basicamente o mesmo desempenho).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(...)]
.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.Considero que a maneira mais pitônica é usar uma compreensão de lista em vez de
map
efilter
. A razão é que as compreensões da lista são mais claras quemap
efilter
.Como você vê, uma compreensão não requer
lambda
expressões extras conforme asmap
necessidades. Além disso, uma compreensão também permite filtrar facilmente, enquantomap
requerfilter
permitir a filtragem.fonte
Eu tentei o código por @ alex-martelli, mas encontrei algumas discrepâncias
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.
fonte
map
retorna uma lista. No Python 3,map
é avaliado preguiçosamente; portanto, simplesmente chamarmap
não computa nenhum dos novos elementos da lista, portanto, por que você obtém tempos tão curtos.