Fungsi Lambda dalam daftar pemahaman

150

Mengapa output dari dua berikut daftar comprehensions berbeda, meskipun fdan lambdafungsi yang sama?

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

dan

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

Pikiran Anda, keduanya type(f)dan type(lambda x: x*x)kembali jenis yang sama.

pengguna763191
sumber
[lambda x: x*x for x in range(10)]lebih cepat dari yang pertama, karena tidak memanggil fungsi loop luar, f berulang kali.
riza
@ Selelinap: ... tidak, sebagai gantinya Anda sedang membuat fungsi baru yang memukul setiap kali melalui loop. ... dan overhead untuk membuat fungsi baru ini, lalu menelepon sedikit lebih lambat (pada sistem saya).
Gerrat
@ Gerrat: Bahkan dengan overhead, masih lebih cepat. Tapi, tentu saja [x*x for x in range(10)]lebih baik.
riza
34
Saya baru saja masuk di sini untuk mendapatkan akses google foobar :)
Gal Margalit

Jawaban:

268

Yang pertama membuat fungsi lambda tunggal dan menyebutnya sepuluh kali.

Yang kedua tidak memanggil fungsi. Ini menciptakan 10 fungsi lambda berbeda. Semua itu ada dalam daftar. Untuk membuatnya setara dengan yang pertama yang Anda butuhkan:

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

Atau lebih baik lagi:

[x*x for x in range(10)]
Winston Ewert
sumber
13
Atau map(lambda x: x*x, range(10)), yang mungkin merupakan arti OP pada awalnya.
Daniel Roseman
ya, lambda x: x * x .. (x) tampaknya prinsip.
staticor
[lambda x: x * x untuk x dalam kisaran (10)] pada dasarnya adalah functur di
haskell
@DanielRoseman, atau lebih tepatnya list(map(lambda x: x*x, range(10)))akan memberi Anda[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
rrlamichhane
108

Pertanyaan ini menyentuh bagian yang sangat busuk dari sintaksis Python "terkenal" dan "jelas" - apa yang diutamakan, lambda, atau untuk pemahaman daftar.

Saya tidak berpikir tujuan OP adalah untuk menghasilkan daftar kotak dari 0 hingga 9. Jika itu masalahnya, kami dapat memberikan lebih banyak solusi:

squares = []
for x in range(10): squares.append(x*x)
  • ini adalah cara sintaksis imperatif yang baik.

Tapi bukan itu intinya. Intinya adalah W (hy) TF apakah ekspresi ambigu ini begitu kontra-intuitif? Dan saya punya kasus idiot untuk Anda pada akhirnya, jadi jangan abaikan jawaban saya terlalu dini (saya sudah ada di wawancara kerja).

Jadi, pemahaman OP mengembalikan daftar lambda:

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

Ini tentu saja hanya 10 salinan berbeda dari fungsi kuadrat, lihat:

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

Catat alamat memori dari lambda - semuanya berbeda!

Anda tentu saja dapat memiliki versi yang lebih "optimal" (haha) dari ungkapan ini:

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

Lihat? 3 kali lambda yang sama .

Harap dicatat, bahwa saya digunakan _sebagaifor variabel. Ini tidak ada hubungannya dengan xdi lambda(itu dibayangi secara leksikal!). Mengerti?

Saya meninggalkan diskusi, mengapa prioritas sintaksinya tidak begitu, bahwa itu semua berarti:

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

yang bisa berupa:, [[0, 1, 4, ..., 81]]atau [(0, 1, 4, ..., 81)], atau yang menurut saya paling logis , ini akan menjadi listelemen 1 - agenerator mengembalikan nilai. Bukan itu masalahnya, bahasanya tidak bekerja seperti ini.

TAPI Apa, Jika ...

Bagaimana jika Anda TIDAK menaungi forvariabel, DAN menggunakannya dalam file Anda lambda???

Nah, kemudian omong kosong terjadi. Lihat ini:

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

ini tentu saja berarti:

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

TETAPI TIDAK BERARTI:

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

Ini gila!

Lambda dalam daftar pemahaman adalah penutupan atas ruang lingkup pemahaman ini. Sebuah leksikal penutupan, sehingga mereka mengacu padai melalui referensi, dan tidak nilainya ketika mereka dievaluasi!

Jadi, ungkapan ini:

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

Kira-kira SETARA untuk:

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

Saya yakin kita bisa melihat lebih banyak di sini menggunakan decompiler python (yang saya maksud misalnya dis modul), tetapi untuk diskusi Python-VM-agnostik ini sudah cukup. Begitu banyak untuk pertanyaan wawancara kerja.

Sekarang, bagaimana membuat listlambda multiplier, yang benar-benar dikalikan dengan integer berturut-turut? Nah, mirip dengan jawaban yang diterima, kita perlu memutuskan hubungan langsung idengan membungkusnya dengan yang lain lambda, yang dipanggil ke dalam dalam ekspresi daftar pemahaman:

Sebelum:

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

Setelah:

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

(Saya memiliki variabel lambda luar juga = i, tapi saya memutuskan ini adalah solusi yang lebih jelas - saya perkenalkany agar kita semua bisa melihat penyihir mana yang mana).

Edit 2019-08-30:

Mengikuti saran oleh @josoler, yang juga hadir dalam jawaban oleh @sheridp - nilai dari daftar pemahaman "variabel loop" dapat "tertanam" di dalam suatu objek - kuncinya adalah untuk diakses pada waktu yang tepat. Bagian "Setelah" di atas melakukannya dengan membungkusnya dengan yang lain lambdadan memanggilnya segera dengan nilai saat ini dari i. Cara lain (sedikit lebih mudah dibaca - tidak menghasilkan efek 'WAT') adalah untuk menyimpan nilai idi dalam partialobjek, dan meminta "inner" (asli) lambdamenganggapnya sebagai argumen (disahkan olehpartial objek di waktu panggilan), yaitu:

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

Hebat, tetapi masih ada sedikit twist untuk Anda! Katakanlah kita tidak ingin membuatnya lebih mudah pada pembaca kode, dan meneruskan faktor dengan nama (sebagai argumen kata kunci ke partial). Mari kita lakukan penggantian nama:

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

Tunggu ... Kami mengubah jumlah argumen dengan 1, dan beralih dari "terlalu banyak" menjadi "terlalu sedikit"?

Yah, itu bukan WAT asli, ketika kita masuk coefdengan partialcara ini, itu menjadi argumen kata kunci, jadi harus muncul setelah xargumen posisi , seperti:

Setelah 3:

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

Saya lebih suka versi terakhir daripada lambda bersarang, tetapi untuk masing-masing ...

Tomasz Gandor
sumber
22
itu adalah pertanyaan wawancara kerja yang kejam dan tidak biasa.
szeitlin
1
Jika kolega saya tidak bertanya, saya mungkin tidak akan pernah mencari jawaban ini
piggybox
8
Wow. Saya baru saja digigit oleh perilaku absurd ini. Terima kasih atas kiriman Anda!
Semut
1
Jawaban yang sangat bagus. Saya hanya mengalami masalah ini juga. Di satu sisi itu memang menunjuk pada batasan Python, tetapi di sisi lain mungkin juga menjadi indikator bau kode. Saya menggunakan solusi ini untuk proyek mainan, tetapi mungkin itu merupakan sinyal untuk merestrukturisasi dalam lingkungan produksi.
ahota
2
Demi kejelasan dan kelengkapan Anda dapat menulis pemahaman daftar terakhir seperti:[partial(lambda i, x: i * x, i) for i in (1, 2)]
josoler
19

Perbedaan besar adalah bahwa contoh pertama benar-benar memanggil lambda f(x) , sedangkan contoh kedua tidak.

Contoh pertama Anda sama dengan [(lambda x: x*x)(x) for x in range(10)]sedangkan contoh kedua Anda sama dengan [f for x in range(10)].

Gabe
sumber
11

Yang pertama

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

berjalan f()untuk setiap nilai dalam rentang sehingga f(x)untuk setiap nilai

yang kedua

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

menjalankan lambda untuk setiap nilai dalam daftar, sehingga menghasilkan semua fungsi tersebut.

Zellio
sumber
11

Orang-orang memberikan jawaban yang baik tetapi lupa menyebutkan bagian terpenting menurut saya: Pada contoh kedua X, pemahaman daftar BUKAN sama Xdengan lambdafungsi, mereka sama sekali tidak berhubungan. Jadi contoh kedua sebenarnya sama dengan:

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

Iterasi internal aktif range(10) hanya bertanggung jawab untuk membuat 10 fungsi lambda serupa dalam daftar (10 fungsi terpisah tetapi sama sekali sama - mengembalikan kekuatan 2 dari setiap input).

Di sisi lain, contoh pertama bekerja sangat berbeda, karena X dari iterasi DO berinteraksi dengan hasil, untuk setiap iterasi nilainya X*Xjadi hasilnya akan menjadi[0,1,4,9,16,25, 36, 49, 64 ,81]

Seidhar seifer
sumber
Ini poin penting. Saya membesarkan hati Anda dan menjelaskannya dalam jawaban saya.
Tomasz Gandor
6

Jawaban lainnya benar, tetapi jika Anda mencoba membuat daftar fungsi, masing-masing dengan parameter yang berbeda, yang dapat dieksekusi nanti , kode berikut akan melakukannya:

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]

Sementara contoh dibuat, saya menemukan itu berguna ketika saya ingin daftar fungsi yang masing-masing mencetak sesuatu yang berbeda, yaitu

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

for i in a:
    i()
sheridp
sumber