Oke, bersabarlah dengan ini, saya tahu ini akan terlihat sangat berbelit-belit, tapi tolong bantu saya memahami apa yang terjadi.
from functools import partial
class Cage(object):
def __init__(self, animal):
self.animal = animal
def gotimes(do_the_petting):
do_the_petting()
def get_petters():
for animal in ['cow', 'dog', 'cat']:
cage = Cage(animal)
def pet_function():
print "Mary pets the " + cage.animal + "."
yield (animal, partial(gotimes, pet_function))
funs = list(get_petters())
for name, f in funs:
print name + ":",
f()
Memberikan:
cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.
Jadi pada dasarnya, mengapa saya tidak mendapatkan tiga hewan yang berbeda? Bukankah cage
'dikemas' ke dalam lingkup lokal dari fungsi bersarang? Jika tidak, bagaimana panggilan ke fungsi bersarang mencari variabel lokal?
Saya tahu bahwa mengalami masalah seperti ini biasanya berarti seseorang 'melakukannya dengan salah', tetapi saya ingin memahami apa yang terjadi.
for animal in ['cat', 'dog', 'cow']
... Saya yakin seseorang akan datang dan menjelaskan hal ini - ini salah satu dari Python gotcha :)Jawaban:
Fungsi bertingkat mencari variabel dari lingkup induk saat dijalankan, bukan saat ditentukan.
Badan fungsi dikompilasi, dan variabel 'bebas' (tidak ditentukan dalam fungsi itu sendiri oleh penetapan), diverifikasi, lalu diikat sebagai sel penutup ke fungsi, dengan kode menggunakan indeks untuk mereferensikan setiap sel.
pet_function
sehingga memiliki satu variabel bebas (cage
) yang kemudian direferensikan melalui sel penutup, indeks 0. Penutup itu sendiri menunjuk ke variabel lokalcage
dalamget_petters
fungsi tersebut.Ketika Anda benar-benar memanggil fungsi tersebut, closure tersebut kemudian digunakan untuk melihat nilai
cage
dalam lingkup sekitarnya pada saat Anda memanggil fungsi tersebut . Di sinilah letak masalahnya. Pada saat Anda memanggil fungsi Anda,get_petters
fungsi tersebut sudah selesai menghitung hasilnya. Thecage
variabel lokal di beberapa titik selama eksekusi yang ditugaskan masing-masing'cow'
,'dog'
dan'cat'
string, tapi pada akhir fungsi,cage
berisi bahwa nilai terakhir'cat'
. Jadi, saat Anda memanggil setiap fungsi yang dikembalikan secara dinamis, Anda mendapatkan nilai yang'cat'
dicetak.Solusinya adalah tidak bergantung pada penutupan. Sebagai gantinya, Anda dapat menggunakan fungsi parsial , membuat cakupan fungsi baru , atau mengikat variabel sebagai nilai default untuk parameter kata kunci .
Contoh fungsi parsial, menggunakan
functools.partial()
:Membuat contoh lingkup baru:
Mengikat variabel sebagai nilai default untuk parameter kata kunci:
Tidak perlu mendefinisikan
scoped_cage
fungsi dalam loop, kompilasi hanya dilakukan satu kali, tidak pada setiap iterasi loop.sumber
Pemahaman saya adalah bahwa kandang dicari di namespace fungsi induk ketika pet_function yang dihasilkan sebenarnya dipanggil, bukan sebelumnya.
Jadi, saat Anda melakukannya
Anda menghasilkan 3 fungsi yang akan menemukan kandang yang terakhir dibuat.
Jika Anda mengganti loop terakhir Anda dengan:
Anda benar-benar akan mendapatkan:
sumber
Ini berasal dari berikut ini
setelah mengulang nilai
i
disimpan dengan malas sebagai nilai akhirnya.Sebagai generator, fungsi akan bekerja (yaitu mencetak setiap nilai secara bergiliran), tetapi ketika mentransformasikannya ke daftar, fungsi tersebut berjalan di atas generator , maka semua panggilan ke
cage
(cage.animal
) mengembalikan kucing.sumber
Mari sederhanakan pertanyaannya. Menetapkan:
Kemudian, seperti dalam pertanyaan, kita mendapatkan:
Tetapi jika kita menghindari membuat yang
list()
pertama:Apa yang sedang terjadi? Mengapa perbedaan halus ini benar-benar mengubah hasil kami?
Jika kita lihat
list(get_petters())
, jelas dari alamat memori yang berubah bahwa kita memang menghasilkan tiga fungsi yang berbeda:Namun, perhatikan
cell
bahwa fungsi-fungsi ini terikat:Untuk kedua loop,
cell
objek tetap sama selama iterasi. Namun, seperti yang diharapkan,str
referensi spesifik itu bervariasi di loop kedua. Thecell
objek mengacu padaanimal
, yang dibuat ketikaget_petters()
disebut. Namun,animal
mengubah apastr
objek yang dirujuknya saat fungsi generator berjalan .Di loop pertama, selama setiap iterasi, kami membuat semua file
f
s, tetapi kita hanya memanggilnya setelah generatorget_petters()
benar-benar habis dan alist
dari fungsi sudah dibuat.Di loop kedua, selama setiap iterasi, kami menjeda file
get_petters()
generator dan memanggilf
setelah setiap jeda. Jadi, kami akhirnya mengambil nilaianimal
pada saat fungsi generator dihentikan sementara.Seperti yang @Claudiu berikan untuk menjawab pertanyaan serupa :
sumber