Funzione lambda nella comprensione dell'elenco


150

Perché l'output delle seguenti due comprensioni comprende differenti, anche se fla lambdafunzione è la stessa?

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

e

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

Intendiamoci entrambi type(f)e type(lambda x: x*x)restituite lo stesso tipo.


[lambda x: x*x for x in range(10)]è più veloce del primo, poiché non chiama ripetutamente una funzione di loop esterno.
Riza,

@Selinap: ... no, invece stai creando una nuova funzione che arricchisce ogni volta il ciclo. ... e il sovraccarico di creare questa nuova funzione, quindi chiamare è un po 'più lento (sul mio sistema comunque).
Gerrat,

@Gerrat: anche con le spese generali, è ancora più veloce. Ma ovviamente [x*x for x in range(10)]è meglio.
Riza,

34
Sono appena entrato qui per ottenere l'accesso a Google Foobar :)
Gal Margalit,

Risposte:


268

Il primo crea una singola funzione lambda e la chiama dieci volte.

Il secondo non chiama la funzione. Crea 10 diverse funzioni lambda. Mette tutti quelli in un elenco. Per renderlo equivalente al primo è necessario:

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

O meglio ancora:

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

13
O map(lambda x: x*x, range(10)), che era probabilmente ciò che l'OP ha significato in primo luogo.
Daniel Roseman,

sì, lambda x: x * x .. (x) sembra principio.
staticor,

[lambda x: x * x per x nel range (10)] è fondamentalmente un functur in haskell
beeri,

@DanielRoseman, o per essere precisi, list(map(lambda x: x*x, range(10)))ti darà[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
rrlamichhane il

108

Questa domanda tocca una parte molto puzzolente della sintassi "famosa" e "ovvia" di Python - ciò che ha la precedenza, la lambda o la comprensione della lista.

Non penso che lo scopo del PO fosse quello di generare un elenco di quadrati da 0 a 9. In tal caso, potremmo dare ancora più soluzioni:

squares = []
for x in range(10): squares.append(x*x)
  • questo è il buon vecchio modo di sintassi imperativa.

Ma non è questo il punto. Il punto è che W (hy) TF è questa espressione ambigua così controintuitiva? E alla fine ho un caso idiota per te, quindi non respingere la mia risposta troppo presto (l'ho fatta durante un colloquio di lavoro).

Pertanto, la comprensione del PO ha restituito un elenco di lambda:

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

Questo è ovviamente solo 10 copie diverse della funzione di quadratura, vedi:

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

Nota gli indirizzi di memoria dei lambda: sono tutti diversi!

Ovviamente potresti avere una versione più "ottimale" (haha) di questa espressione:

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

Vedere? 3 volte lo stesso lambda.

Si noti che ho usato _come forvariabile. Non ha nulla a che fare con il xin lambda(è oscurato in modo lessicale!). Prendilo?

Lascio fuori la discussione, perché la precedenza della sintassi non è così, che significava tutto:

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

quale potrebbe essere:, [[0, 1, 4, ..., 81]]o [(0, 1, 4, ..., 81)], o che trovo più logico , questo sarebbe un listelemento di 1 - a generatorrestituire i valori. Non è proprio il caso, la lingua non funziona in questo modo.

MA cosa, se ...

Che cosa succede se NON offuschi la forvariabile e la usi nella tua lambdas ???

Bene, allora succede una schifezza. Guarda questo:

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

questo significa ovviamente:

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

MA NON significa:

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

Questo è semplicemente pazzo!

I lambda nella comprensione dell'elenco sono una chiusura del campo di applicazione di questa comprensione. Una chiusura lessicale , quindi si riferiscono al iriferimento via e non al suo valore quando sono stati valutati!

Quindi, questa espressione:

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

È approssimativamente equivalente a:

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

Sono sicuro che potremmo vedere di più qui usando un decompilatore python (con il quale intendo ad esempio il dismodulo), ma per la discussione agnostica su Python-VM questo è sufficiente. Questo per quanto riguarda la domanda del colloquio di lavoro.

Ora, come creare un listmoltiplicatore lambda, che si moltiplica davvero per numeri interi consecutivi? Bene, analogamente alla risposta accettata, dobbiamo spezzare il legame diretto iavvolgendolo in un altro lambda, che viene chiamato all'interno dell'espressione di comprensione dell'elenco:

Prima:

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

Dopo:

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

(Avevo anche la variabile lambda esterna = i, ma ho deciso che questa è la soluzione più chiara - ho introdotto in ymodo che tutti possiamo vedere quale strega è quale).

Modifica 30-08-2019:

Seguendo un suggerimento di @josoler, che è anche presente in una risposta di @sheridp - il valore della "variabile di loop" di comprensione dell'elenco può essere "incorporato" all'interno di un oggetto - la chiave è che sia possibile accedervi al momento giusto. La sezione "Dopo" sopra lo fa avvolgendolo in un altro lambdae chiamandolo immediatamente con il valore corrente di i. Un altro modo (un po 'più semplice da leggere - non produce alcun effetto "WAT") è quello di memorizzare il valore iall'interno di un partialoggetto e far sì che "interno" (originale) lo lambdaprenda come argomento (passato fornito partialdall'oggetto al ora della chiamata), ovvero:

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

Fantastico, ma c'è ancora una piccola svolta per te! Supponiamo che non vogliamo semplificare il lettore di codice e passare il fattore per nome (come argomento di parola chiave a partial). Facciamo un po 'di rinomina:

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

Aspetta ... Stiamo cambiando il numero di argomenti di 1 e passando da "troppi" a "troppo pochi"?

Beh, non è un vero e proprio WAT, quando si passa coefa partialin questo modo, diventa un argomento parola chiave, quindi deve venire dopo la posizionale xargomento, in questo modo:

Dopo 3:

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

Preferirei l'ultima versione al lambda nidificato, ma a ciascuno il suo ...


22
questa è una domanda di colloquio di lavoro crudele e insolita.
szeitlin,

1
Se il mio collega non lo avesse chiesto, probabilmente non avrei mai cercato questa risposta
salvadanaio il

8
Wow. Sono stato morso male da questo comportamento assurdo. Grazie per il tuo post!
Formica,

1
Risposta eccellente. Mi sono appena imbattuto anche in questo problema. Da un lato indica una limitazione di Python, ma dall'altro potrebbe anche essere un indicatore di odore di codice. Sto usando questa soluzione per un progetto giocattolo, ma potrebbe essere un segnale per ristrutturare in un ambiente di produzione.
ahota,

2
Per motivi di chiarezza e completezza potresti scrivere l'ultima comprensione della lista come:[partial(lambda i, x: i * x, i) for i in (1, 2)]
josoler

19

La grande differenza è che il primo esempio in realtà invoca la lambda f(x), mentre il secondo esempio no.

Il tuo primo esempio è equivalente a [(lambda x: x*x)(x) for x in range(10)]mentre il secondo esempio è equivalente a [f for x in range(10)].


11

Il primo

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

viene eseguito f()per ciascun valore nell'intervallo, quindi lo fa f(x)per ciascun valore

il secondo

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

esegue lambda per ciascun valore nell'elenco, quindi genera tutte quelle funzioni.


11

Le persone hanno dato buone risposte ma hanno dimenticato di menzionare la parte più importante secondo me: nel secondo esempio la Xcomprensione della lista NON è la stessa Xdella lambdafunzione, sono totalmente indipendenti. Quindi il secondo esempio è in realtà lo stesso di:

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

Le iterazioni interne range(10)sono responsabili solo della creazione di 10 funzioni lambda simili in un elenco (10 funzioni separate ma totalmente simili - restituendo la potenza 2 di ciascun ingresso).

D'altra parte, il primo esempio funziona in modo totalmente diverso, poiché la X delle iterazioni interagisce con i risultati, per ogni iterazione il valore è X*Xquindi il risultato sarebbe[0,1,4,9,16,25, 36, 49, 64 ,81]


Questo è un punto importante Ti ho votato e approfondito nella mia risposta.
Tomasz Gandor,

6

Le altre risposte sono corrette, ma se si sta tentando di creare un elenco di funzioni, ognuna con un parametro diverso, che può essere eseguito in un secondo momento , il seguente codice lo farà:

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]

Mentre l'esempio è inventato, l'ho trovato utile quando volevo un elenco di funzioni che ciascuna stampasse qualcosa di diverso, ad es

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

for i in a:
    i()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.