Sementara saya sedang menyelidiki masalah yang saya miliki dengan penutupan leksikal dalam kode Javascript, saya menemukan masalah ini dengan Python:
flist = []
for i in xrange(3):
def func(x): return x * i
flist.append(func)
for f in flist:
print f(2)
Perhatikan bahwa contoh ini dengan sadar menghindari lambda
. Mencetak "4 4 4", yang mengejutkan. Saya harapkan "0 2 4".
Kode Perl yang setara ini melakukannya dengan benar:
my @flist = ();
foreach my $i (0 .. 2)
{
push(@flist, sub {$i * $_[0]});
}
foreach my $f (@flist)
{
print $f->(2), "\n";
}
"0 2 4" dicetak.
Bisakah Anda jelaskan perbedaannya?
Memperbarui:
Masalahnya bukan dengan i
menjadi global. Ini menampilkan perilaku yang sama:
flist = []
def outer():
for i in xrange(3):
def inner(x): return x * i
flist.append(inner)
outer()
#~ print i # commented because it causes an error
for f in flist:
print f(2)
Seperti yang ditunjukkan oleh baris yang dikomentari, i
tidak diketahui pada saat itu. Namun, ia mencetak "4 4 4".
python
closures
lazy-evaluation
late-binding
Eli Bendersky
sumber
sumber
Jawaban:
Python sebenarnya berperilaku seperti yang didefinisikan. Tiga fungsi terpisah dibuat, tetapi masing-masing memiliki penutupan lingkungan yang telah ditentukan - dalam hal ini, lingkungan global (atau lingkungan fungsi luar jika loop ditempatkan di dalam fungsi lain). Ini persis masalah, meskipun - dalam lingkungan ini, saya dimutasi , dan semua penutup mengacu pada i yang sama .
Berikut ini adalah solusi terbaik yang bisa saya buat - buat fungsi creater dan panggil itu . Ini akan memaksa lingkungan yang berbeda untuk setiap fungsi yang dibuat, dengan i berbeda di masing -masing fungsi .
Inilah yang terjadi ketika Anda mencampur efek samping dan pemrograman fungsional.
sumber
def inner(x, i=i): return x * i
Fungsi-fungsi yang didefinisikan dalam loop tetap mengakses variabel yang sama
i
saat nilainya berubah. Pada akhir loop, semua fungsi menunjuk ke variabel yang sama, yang memegang nilai terakhir dalam loop: efeknya adalah apa yang dilaporkan dalam contoh.Untuk mengevaluasi
i
dan menggunakan nilainya, pola umum adalah menetapkannya sebagai parameter default: standar parameter dievaluasi ketikadef
pernyataan dieksekusi, dan dengan demikian nilai variabel loop dibekukan.Berikut ini berfungsi seperti yang diharapkan:
sumber
def
pernyataan dieksekusi /i
dari definisi. :-(Inilah cara Anda melakukannya menggunakan
functools
perpustakaan (yang saya tidak yakin tersedia pada saat pertanyaan diajukan).Output 0 2 4, seperti yang diharapkan.
sumber
functools.partialmethod()
python 3.4Lihat ini:
Ini berarti mereka semua menunjuk ke instance variabel i yang sama, yang akan memiliki nilai 2 setelah loop selesai.
Solusi yang mudah dibaca:
sumber
Apa yang terjadi adalah bahwa variabel i ditangkap, dan fungsinya mengembalikan nilai yang terikat pada saat ia dipanggil. Dalam bahasa fungsional situasi seperti ini tidak pernah muncul, karena saya tidak akan pulih. Namun dengan python, dan juga seperti yang Anda lihat dengan lisp, ini tidak lagi benar.
Perbedaannya dengan contoh skema Anda adalah hubungannya dengan semantik loop do. Skema secara efektif membuat variabel i baru setiap kali melalui loop, daripada menggunakan kembali saya yang mengikat seperti dengan bahasa lain. Jika Anda menggunakan variabel berbeda yang dibuat di luar untuk loop dan mengubahnya, Anda akan melihat perilaku yang sama dalam skema. Coba ganti loop Anda dengan:
Lihatlah di sini untuk diskusi lebih lanjut tentang ini.
[Sunting] Mungkin cara yang lebih baik untuk menggambarkannya adalah dengan menganggap do loop sebagai makro yang melakukan langkah-langkah berikut:
yaitu. setara dengan python di bawah ini:
I bukan lagi yang berasal dari lingkup induk tetapi variabel baru dalam cakupannya sendiri (mis. Parameter ke lambda) dan jadi Anda mendapatkan perilaku yang Anda amati. Python tidak memiliki lingkup baru implisit ini, jadi badan for for hanya membagikan variabel i.
sumber
Saya masih belum sepenuhnya yakin mengapa dalam beberapa bahasa ini bekerja satu arah, dan dalam beberapa cara lain. Dalam Common Lisp itu seperti Python:
Mencetak "6 6 6" (perhatikan bahwa di sini daftarnya adalah dari 1 hingga 3, dan dibangun secara terbalik "). Sementara di Skema berfungsi seperti di Perl:
Cetakan "6 4 2"
Dan seperti yang telah saya sebutkan, Javascript ada di Python / CL camp. Tampaknya ada keputusan implementasi di sini, yang pendekatan bahasa berbeda dengan cara yang berbeda. Saya ingin sekali memahami apa keputusannya.
sumber
Masalahnya adalah bahwa semua fungsi lokal mengikat ke lingkungan yang sama dan dengan demikian ke
i
variabel yang sama . Solusinya (solusinya) adalah membuat lingkungan yang terpisah (stack frames) untuk setiap fungsi (atau lambda):sumber
Variabelnya
i
adalah global, yang nilainya 2 pada setiap kali fungsif
dipanggil.Saya akan cenderung menerapkan perilaku yang Anda cari sebagai berikut:
Respons terhadap pembaruan Anda : Bukan globalitas
i
per se yang menyebabkan perilaku ini, melainkan fakta bahwa itu adalah variabel dari cakupan terlampir yang memiliki nilai tetap selama waktu ketika f dipanggil. Dalam contoh kedua Anda, nilaii
diambil dari ruang lingkupkkk
fungsi, dan tidak ada yang berubah ketika Anda memanggil fungsiflist
.sumber
Alasan di balik perilaku telah dijelaskan, dan beberapa solusi telah diposting, tetapi saya pikir ini adalah yang paling pythonic (ingat, segala sesuatu di Python adalah objek!):
Jawaban Claudiu cukup bagus, menggunakan generator fungsi, tetapi jawaban piro adalah hack, jujur, karena itu membuat saya menjadi argumen "tersembunyi" dengan nilai default (itu akan berfungsi dengan baik, tetapi itu bukan "pythonic") .
sumber
func
dalamx * func.i
akan selalu mengacu pada fungsi terakhir yang ditetapkan. Jadi, meskipun masing-masing fungsi secara individual memiliki angka yang benar, masing-masing akhirnya membaca dari yang terakhir.