Função Lambda em compreensões de lista


149

Por que a saída das duas compreensões de lista a seguir é diferente, embora fa lambdafunção seja a mesma?

f = lambda x: x*x
[f(x) for x in range(10)]

e

[lambda x: x*x for x in range(10)]

Lembre-se, ambos type(f)e type(lambda x: x*x)retorne o mesmo tipo.


[lambda x: x*x for x in range(10)]é mais rápido que o primeiro, pois não chama uma função de loop externo, f repetidamente.
Riza

@Selinap: ... não, em vez disso, você está criando uma nova função de marca toda vez que passa pelo loop. ... e a sobrecarga de criação dessa nova função, a chamada é um pouco mais lenta (no meu sistema, de qualquer maneira).
Gerrat

@Gerrat: Mesmo com sobrecarga, ainda é mais rápido. Mas, claro, [x*x for x in range(10)]é melhor.
Riza

34
Eu só entrou aqui para obter o Google acesso foobar :)
Gal Margalit

Respostas:


267

O primeiro cria uma única função lambda e a chama dez vezes.

O segundo não chama a função. Cria 10 funções lambda diferentes. Coloca todos aqueles em uma lista. Para torná-lo equivalente ao primeiro, você precisa:

[(lambda x: x*x)(x) for x in range(10)]

Ou melhor ainda:

[x*x for x in range(10)]

13
Ou map(lambda x: x*x, range(10)), provavelmente, o que o OP significava em primeiro lugar.
Daniel Roseman 20/05

sim, lambda x: x * x .. (x) parece princípio.
Static # 19/13

[lambda x: x * x para x no intervalo (10)] é basicamente um functur em haskell
moshe beeri 29/08/2015

@DanielRoseman, ou para ser mais preciso list(map(lambda x: x*x, range(10))), dará a você[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
rrlamichhane

108

Essa pergunta toca uma parte muito fedorenta da sintaxe "famosa" e "óbvia" do Python - o que tem precedência, a lambda ou a compreensão da lista.

Não acho que o objetivo do OP fosse gerar uma lista de quadrados de 0 a 9. Se esse fosse o caso, poderíamos dar ainda mais soluções:

squares = []
for x in range(10): squares.append(x*x)
  • esse é o bom e velho caminho da sintaxe imperativa.

Mas não é esse o ponto. O ponto é W (hy) TF. Essa expressão ambígua é tão contra-intuitiva? E eu tenho um caso idiota para você no final, então não descarte minha resposta muito cedo (eu a tive em uma entrevista de emprego).

Portanto, a compreensão do OP retornou uma lista de lambdas:

[(lambda x: x*x) for x in range(10)]

Obviamente, são apenas 10 cópias diferentes da função quadratura, veja:

>>> [lambda x: x*x for _ in range(3)]
[<function <lambda> at 0x00000000023AD438>, <function <lambda> at 0x00000000023AD4A8>, <function <lambda> at 0x00000000023AD3C8>]

Observe os endereços de memória dos lambdas - todos são diferentes!

Obviamente, você poderia ter uma versão mais "ótima" (haha) dessa expressão:

>>> [lambda x: x*x] * 3
[<function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>]

Vejo? 3 vezes a mesma lambda.

Por favor, note que eu usei _como forvariável. Não tem nada a ver com o xno lambda(é ofuscado lexicamente!). Pegue?

Estou deixando de fora a discussão, por que a precedência da sintaxe não é assim, e tudo isso significava:

[lambda x: (x*x for x in range(10))]

que poderia ser:, [[0, 1, 4, ..., 81]]ou [(0, 1, 4, ..., 81)], ou que eu acho mais lógico , esse seria um listelemento de 1 - um generatorretorno dos valores. Simplesmente não é o caso, a linguagem não funciona dessa maneira.

MAS o que, se ...

E se você não ofuscar a forvariável, e usá-lo no seu lambdas ??

Bem, então porcaria acontece. Veja isso:

[lambda x: x * i for i in range(4)]

isto significa, é claro:

[(lambda x: x * i) for i in range(4)]

MAS NÃO significa:

[(lambda x: x * 0), (lambda x: x * 1), ... (lambda x: x * 3)]

Isso é loucura!

As lambdas na compreensão da lista são um fechamento sobre o escopo dessa compreensão. Um fechamento lexical , para que eles se refiram à ireferência via, e não ao seu valor quando foram avaliados!

Então, esta expressão:

[(lambda x: x * i) for i in range(4)]

É aproximadamente equivalente a:

[(lambda x: x * 3), (lambda x: x * 3), ... (lambda x: x * 3)]

Tenho certeza de que poderíamos ver mais aqui usando um descompilador python (com o que quero dizer, por exemplo, o dismódulo), mas para a discussão independente de Python-VM isso é suficiente. Tanta coisa para a pergunta da entrevista de emprego.

Agora, como fazer um listmultiplicador de lambdas, que realmente se multiplica por números inteiros consecutivos? Bem, da mesma forma que a resposta aceita, precisamos quebrar o vínculo direto ienvolvendo-o em outro lambda, que está sendo chamado dentro da expressão de compreensão da lista:

Antes:

>>> a = [(lambda x: x * i) for i in (1, 2)]
>>> a[1](1)
2
>>> a[0](1)
2

Depois de:

>>> a = [(lambda y: (lambda x: y * x))(i) for i in (1, 2)]
>>> a[1](1)
2
>>> a[0](1)
1

(Eu também tinha a variável lambda externa = = i, mas decidi que essa é a solução mais clara - apresentei ypara que todos possamos ver qual bruxa é qual).

Editar 2019-08-30:

Seguindo uma sugestão de @josoler, que também está presente em uma resposta de @sheridp - o valor da "variável de loop" de compreensão da lista pode ser "incorporado" dentro de um objeto - a chave é que ele seja acessado no momento certo. A seção "Depois" acima faz isso envolvendo-o em outro lambdae chamando-o imediatamente com o valor atual de i. Outra maneira (um pouco mais fácil de ler - não produz efeito 'WAT') é armazenar o valor de identro de um partialobjeto e fazer com que o "interno" (original) o lambdaleve como argumento (transmitido pelo partialobjeto no hora da chamada), ou seja:

Após 2:

>>> from functools import partial
>>> a = [partial(lambda y, x: y * x, i) for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)

Ótimo, mas ainda há uma pequena reviravolta para você! Digamos que não queremos facilitar o leitor de código e passar o fator pelo nome (como argumento de palavra-chave partial). Vamos fazer uma renomeação:

Após 2,5:

>>> a = [partial(lambda coef, x: coef * x, coef=i) for i in (1, 2)]
>>> a[0](1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: <lambda>() got multiple values for argument 'coef'

WAT?

>>> a[0]()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: <lambda>() missing 1 required positional argument: 'x'

Espere ... Estamos alterando o número de argumentos por 1 e passando de "muitos" para "poucos"?

Bem, não é uma verdadeira WAT, quando passamos coefpara partial, desta forma, torna-se um argumento de palavra-chave, por isso deve vir após o posicional xargumento, assim:

Após 3:

>>> a = [partial(lambda x, coef: coef * x, coef=i) for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)

Eu preferiria a última versão sobre o lambda aninhado, mas cada um na sua ...


22
essa é uma pergunta de entrevista de emprego cruel e incomum.
Szeitlin

1
Se o meu colega não pediu, eu provavelmente nunca iria procurar esta resposta
piggybox

8
Uau. Acabei de ser mordido por esse comportamento absurdo. Obrigado pelo seu post!
Ant

1
Excelente resposta. Eu acabei de encontrar esse problema também. Por um lado, aponta para uma limitação do Python, mas, por outro lado, também pode ser um indicador de cheiro de código. Estou usando esta solução para um projeto de brinquedo, mas pode ser um sinal de reestruturação em um ambiente de produção.
Ahota

2
Por uma questão de clareza e completude você poderia escrever a última compreensão da lista, como:[partial(lambda i, x: i * x, i) for i in (1, 2)]
josoler

19

A grande diferença é que o primeiro exemplo realmente chama o lambda f(x), enquanto o segundo exemplo não.

Seu primeiro exemplo é equivalente a [(lambda x: x*x)(x) for x in range(10)]enquanto o segundo exemplo é equivalente a [f for x in range(10)].


11

O primeiro

f = lambda x: x*x
[f(x) for x in range(10)]

é executado f()para cada valor no intervalo, assim como f(x)para cada valor

o segundo

[lambda x: x*x for x in range(10)]

executa o lambda para cada valor na lista, portanto, gera todas essas funções.


11

As pessoas deram boas respostas, mas esqueceram de mencionar a parte mais importante na minha opinião: No segundo exemplo, a Xcompreensão da lista NÃO é a mesma Xda lambdafunção, elas são totalmente independentes. Portanto, o segundo exemplo é realmente o mesmo que:

[Lambda X: X*X for I in range(10)]

As iterações internas ativadas range(10)são responsáveis ​​apenas pela criação de 10 funções lambda semelhantes em uma lista (10 funções separadas, mas totalmente similares - retornando a potência 2 de cada entrada).

Por outro lado, o primeiro exemplo funciona totalmente diferente, porque o X das iterações interage com os resultados, para cada iteração o valor é, X*Xportanto, o resultado seria[0,1,4,9,16,25, 36, 49, 64 ,81]


esse é um ponto importante. Eu votei em você e o elaborei na minha resposta.
Tomasz Gandor

6

As outras respostas estão corretas, mas se você estiver tentando fazer uma lista de funções, cada uma com um parâmetro diferente, que pode ser executado posteriormente , o seguinte código fará isso:

import functools
a = [functools.partial(lambda x: x*x, x) for x in range(10)]

b = []
for i in a:
    b.append(i())

In [26]: b
Out[26]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Enquanto o exemplo é artificial, achei útil quando eu queria uma lista de funções em que cada uma imprimisse algo diferente, ou seja,

import functools
a = [functools.partial(lambda x: print(x), x) for x in range(10)]

for i in a:
    i()
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.