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 for
variável. Não tem nada a ver com o x
no 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 list
elemento de 1 - um generator
retorno 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 for
variável, e usá-lo no seu lambda
s ??
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 à i
referê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 dis
mó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 list
multiplicador 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 i
envolvendo-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 y
para 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 lambda
e 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 i
dentro de um partial
objeto e fazer com que o "interno" (original) o lambda
leve como argumento (transmitido pelo partial
objeto 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 coef
para partial
, desta forma, torna-se um argumento de palavra-chave, por isso deve vir após o posicional x
argumento, 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 ...
[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.