Saya membutuhkan fungsi panggilan balik yang hampir persis sama untuk serangkaian acara gui. Fungsi ini akan berperilaku sedikit berbeda bergantung pada peristiwa mana yang memanggilnya. Sepertinya kasus yang sederhana bagi saya, tetapi saya tidak dapat memahami perilaku aneh fungsi lambda ini.
Jadi saya memiliki kode yang disederhanakan berikut ini:
def callback(msg):
print msg
#creating a list of function handles with an iterator
funcList=[]
for m in ('do', 're', 'mi'):
funcList.append(lambda: callback(m))
for f in funcList:
f()
#create one at a time
funcList=[]
funcList.append(lambda: callback('do'))
funcList.append(lambda: callback('re'))
funcList.append(lambda: callback('mi'))
for f in funcList:
f()
Output dari kode ini adalah:
mi
mi
mi
do
re
mi
Saya mengharapkan:
do
re
mi
do
re
mi
Mengapa menggunakan iterator mengacaukan segalanya?
Saya sudah mencoba menggunakan deepcopy:
import copy
funcList=[]
for m in ('do', 're', 'mi'):
funcList.append(lambda: callback(copy.deepcopy(m)))
for f in funcList:
f()
Tetapi ini memiliki masalah yang sama.
python
lexical-closures
agartland
sumber
sumber
Jawaban:
Masalahnya di sini adalah
m
variabel (referensi) yang diambil dari lingkup sekitarnya. Hanya parameter yang disimpan dalam lingkup lambda.Untuk mengatasi ini, Anda harus membuat ruang lingkup lain untuk lambda:
def callback(msg): print msg def callback_factory(m): return lambda: callback(m) funcList=[] for m in ('do', 're', 'mi'): funcList.append(callback_factory(m)) for f in funcList: f()
Dalam contoh di atas, lambda juga menggunakan cakupan sekitarnya untuk menemukan
m
, tetapi kali inicallback_factory
cakupannya yang dibuat sekali untuk setiapcallback_factory
panggilan.Atau dengan functools.pihak :
from functools import partial def callback(msg): print msg funcList=[partial(callback, m) for m in ('do', 're', 'mi')] for f in funcList: f()
sumber
Ketika lambda dibuat, itu tidak membuat salinan dari variabel dalam lingkup pelingkupan yang digunakannya. Ini memelihara referensi ke lingkungan sehingga dapat mencari nilai variabel nanti. Hanya ada satu
m
. Itu akan ditugaskan setiap kali melalui loop. Setelah pengulangan, variabelm
memiliki nilai'mi'
. Jadi ketika Anda benar-benar menjalankan fungsi yang Anda buat nanti, itu akan mencari nilaim
di lingkungan yang membuatnya, yang kemudian akan memiliki nilai.'mi'
.Satu solusi umum dan idiomatik untuk masalah ini adalah dengan menangkap nilai
m
pada saat lambda dibuat dengan menggunakannya sebagai argumen default dari parameter opsional. Anda biasanya menggunakan parameter dengan nama yang sama sehingga Anda tidak perlu mengubah badan kode:for m in ('do', 're', 'mi'): funcList.append(lambda m=m: callback(m))
sumber
def
deklarasi.Python tentu saja menggunakan referensi, tetapi tidak masalah dalam konteks ini.
Saat Anda mendefinisikan lambda (atau fungsi, karena ini adalah perilaku yang sama persis), ini tidak mengevaluasi ekspresi lambda sebelum runtime:
# defining that function is perfectly fine def broken(): print undefined_var broken() # but calling it will raise a NameError
Bahkan lebih mengejutkan dari contoh lambda Anda:
i = 'bar' def foo(): print i foo() # bar i = 'banana' foo() # you would expect 'bar' here? well it prints 'banana'
Singkatnya, berpikirlah dinamis: tidak ada yang dievaluasi sebelum interpretasi, itulah mengapa kode Anda menggunakan nilai terbaru m.
Ketika mencari m dalam eksekusi lambda, m diambil dari ruang lingkup paling atas, yang berarti, seperti yang ditunjukkan orang lain; Anda dapat menghindari masalah itu dengan menambahkan cakupan lain:
def factory(x): return lambda: callback(x) for m in ('do', 're', 'mi'): funcList.append(factory(m))
Di sini, ketika lambda dipanggil, itu terlihat dalam cakupan definisi lambda untuk x. X ini adalah variabel lokal yang ditentukan dalam tubuh pabrik. Karenanya, nilai yang digunakan pada eksekusi lambda akan menjadi nilai yang diteruskan sebagai parameter selama panggilan ke pabrik. Dan doremi!
Sebagai catatan, saya bisa saja mendefinisikan pabrik sebagai pabrik (m) [ganti x dengan m], perilakunya sama. Saya menggunakan nama yang berbeda untuk kejelasan :)
Anda mungkin menemukan bahwa Andrej Bauer memiliki masalah lambda yang serupa. Yang menarik di blog itu adalah komentarnya, di mana Anda akan belajar lebih banyak tentang penutupan python :)
sumber
Tidak secara langsung terkait dengan masalah yang sedang dihadapi, tetapi sebuah kebijaksanaan yang sangat berharga: Objek Python oleh Fredrik Lundh.
sumber
Ya, itu masalah ruang lingkup, itu mengikat m luar, apakah Anda menggunakan lambda atau fungsi lokal. Sebagai gantinya, gunakan functor:
class Func1(object): def __init__(self, callback, message): self.callback = callback self.message = message def __call__(self): return self.callback(self.message) funcList.append(Func1(callback, m))
sumber
soluiton untuk lambda lebih lambda
In [0]: funcs = [(lambda j: (lambda: j))(i) for i in ('do', 're', 'mi')] In [1]: funcs Out[1]: [<function __main__.<lambda>>, <function __main__.<lambda>>, <function __main__.<lambda>>] In [2]: [f() for f in funcs] Out[2]: ['do', 're', 'mi']
bagian luar
lambda
digunakan untuk mengikat nilai saat inii
kej
disetiap kali luar yang
lambda
disebut itu membuat sebuah instance dari innerlambda
denganj
terikat pada nilai saat ini darii
sebagaii
nilai 'ssumber
Pertama, apa yang Anda lihat bukanlah masalah, dan tidak terkait dengan panggilan-dengan-referensi atau dengan-nilai.
Sintaks lambda yang Anda tentukan tidak memiliki parameter, dan karena itu, cakupan yang Anda lihat dengan parameter
m
berada di luar fungsi lambda. Inilah mengapa Anda melihat hasil ini.Sintaks lambda, dalam contoh Anda tidak diperlukan, dan Anda lebih suka menggunakan panggilan fungsi sederhana:
for m in ('do', 're', 'mi'): callback(m)
Sekali lagi, Anda harus sangat tepat tentang parameter lambda apa yang Anda gunakan dan di mana tepatnya ruang lingkupnya dimulai dan diakhiri.
Sebagai catatan tambahan, tentang penyaluran parameter. Parameter dalam python selalu mengacu pada objek. Mengutip Alex Martelli:
sumber
Variabel
m
sedang ditangkap, jadi ekspresi lambda Anda selalu melihat nilai "saat ini".Jika Anda perlu menangkap nilai secara efektif pada suatu waktu, tulis fungsi yang mengambil nilai yang Anda inginkan sebagai parameter, dan mengembalikan ekspresi lambda. Pada titik itu, lambda akan menangkap nilai parameter , yang tidak akan berubah saat Anda memanggil fungsi beberapa kali:
def callback(msg): print msg def createCallback(msg): return lambda: callback(msg) #creating a list of function handles with an iterator funcList=[] for m in ('do', 're', 'mi'): funcList.append(createCallback(m)) for f in funcList: f()
Keluaran:
sumber
sebenarnya tidak ada variabel dalam pengertian klasik di Python, hanya nama yang telah terikat oleh referensi ke objek yang berlaku. Fungsi genap adalah semacam objek dengan Python, dan lambda tidak membuat pengecualian pada aturan :)
sumber
Sebagai catatan tambahan,
map
meskipun dibenci oleh beberapa tokoh Python terkenal, memaksa konstruksi yang mencegah jebakan ini.fs = map (lambda i: lambda: callback (i), ['do', 're', 'mi'])
NB:
lambda i
tindakan pertama seperti pabrik di jawaban lain.sumber