Coba tiga masalah ini dan kemudian periksa jawabannya di akhir artikel.
Kiat : tugas memiliki kesamaan, jadi segarkan pikiran Anda tentang solusi untuk tugas pertama, saat Anda beralih ke tugas kedua atau ketiga, itu akan lebih mudah bagi Anda.
Tugas pertama
Ada beberapa variabel:
x = 1
y = 2
l = [x, y]
x += 5
a = [1]
b = [2]
s = [a, b]
a.append(5)
Apa yang akan ditampilkan saat mencetak
l
dan s
?
Tugas kedua
Mari kita mendefinisikan fungsi sederhana:
def f(x, s=set()):
s.add(x)
print(s)
Apa yang terjadi jika Anda menelepon:
>>f(7)
>>f(6, {4, 5})
>>f(2)
Tugas ketiga
Kami mendefinisikan dua fungsi sederhana:
def f():
l = [1]
def inner(x):
l.append(x)
return l
return inner
def g():
y = 1
def inner(x):
y += x
return y
return inner
Apa yang kita dapatkan setelah menjalankan perintah ini?
>>f_inner = f()
>>print(f_inner(2))
>>g_inner = g()
>>print(g_inner(2))
Seberapa yakin Anda dalam jawaban Anda? Mari kita periksa kasusmu.
Solusi dari masalah pertama
>>print(l)
[1, 2]
>>print(s)
[[1, 5], [2]]
Mengapa daftar kedua merespons perubahan pada elemen pertama
a.append(5)
, dan apakah daftar pertama sepenuhnya mengabaikan perubahan yang sama x+=5
?
Solusi dari masalah kedua
Mari lihat apa yang terjadi:
>>f(7)
{7}
>>f(6, {4, 5})
{4, 5, 6}
>>f(2)
{2, 7}
Tunggu, bukankah hasil akhirnya
{2}
?
Solusi dari masalah ketiga
Hasilnya akan seperti ini:
>>f_inner = f()
>>print(f_inner(2))
[1, 2]
>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment
Kenapa
g_inner(2)
tidak 3
? Mengapa fungsi batin f()
mengingat lingkup luar, tetapi fungsi batin g()
tidak? Mereka hampir identik!
Penjelasan
Bagaimana jika saya memberi tahu Anda bahwa semua contoh perilaku aneh ini terkait dengan perbedaan antara objek yang dapat berubah dan tidak berubah dalam Python?
Objek yang dapat dimodifikasi, seperti daftar, set, atau kamus, dapat dimodifikasi secara lokal. Objek yang tidak dapat diubah seperti nilai numerik dan string, tupel tidak dapat dimodifikasi; "perubahan" mereka akan mengarah pada penciptaan objek baru.
Penjelasan tugas pertama
x = 1
y = 2
l = [x, y]
x += 5
a = [1]
b = [2]
s = [a, b]
a.append(5)
>>print(l)
[1, 2]
>>print(s)
[[1, 5], [2]]
Karena tidak dapat
x
diubah, operasi x+=5
tidak mengubah objek asli, tetapi membuat yang baru. Tetapi item pertama dalam daftar masih mengacu pada objek asli, sehingga nilainya tidak berubah.
Karena objek yang bisa berubah, perintah
a.append(5)
mengubah objek asli (daripada membuat yang baru), dan daftar s
"melihat" perubahan.
Penjelasan tugas kedua
def f(x, s=set()):
s.add(x)
print(s)
>>f(7)
{7}
>>f(6, {4, 5})
{4, 5, 6}
>>f(2)
{2, 7}
Semuanya jelas dengan dua hasil pertama: nilai pertama
7
ditambahkan ke set awalnya kosong dan ternyata {7}
; maka nilai tersebut 6
ditambahkan ke set {4, 5}
dan diperoleh {4, 5, 6}
.
Dan kemudian hal-hal aneh dimulai. Nilai
2
ditambahkan bukan ke set kosong, tetapi ke {7}. Mengapa? Nilai awal dari parameter opsional s
dihitung hanya sekali: pada panggilan pertama, s akan diinisialisasi sebagai set kosong. Dan karena itu bisa berubah, f(7)
itu akan diubah di tempat setelah dipanggil . Panggilan kedua f(6, {4, 5})
tidak akan memengaruhi parameter default: itu diganti oleh satu set {4, 5}
, yaitu {4, 5}
variabel berbeda. Panggilan ketiga f(2)
menggunakan variabel yang samas
yang digunakan pada panggilan pertama, tetapi tidak diinisialisasi ulang sebagai set kosong, tetapi diambil dari nilai sebelumnya {7}
.
Oleh karena itu, Anda tidak boleh menggunakan argumen yang bisa diubah sebagai argumen default. Dalam hal ini, fungsi perlu diubah:
def f(x, s=None):
if s is None:
s = set()
s.add(x)
print(s)
Penjelasan tugas ketiga
def f():
l = [1]
def inner(x):
l.append(x)
return l
return inner
def g():
y = 1
def inner(x):
y += x
return y
return inner
>>f_inner = f()
>>print(f_inner(2))
[1, 2]
>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment
Di sini kita berhadapan dengan penutupan: fungsi internal mengingat bagaimana ruang nama eksternal mereka terlihat pada saat definisi mereka. Atau setidaknya mereka harus ingat, tetapi fungsi kedua membuat wajah poker dan berperilaku seolah-olah tidak pernah mendengar namespace eksternalnya.
Mengapa ini terjadi? Saat kami mengeksekusi
l.append(x)
, objek yang dapat diubah yang dibuat saat fungsi didefinisikan berubah. Tetapi variabel l
masih merujuk ke alamat lama di memori. Namun, mencoba mengubah variabel yang tidak dapat diubah dalam fungsi kedua y += x
menghasilkan y mulai merujuk ke alamat memori yang berbeda: yang asli akan dilupakan, yang akan menghasilkan UnboundLocalError.
Kesimpulan
Perbedaan antara objek yang dapat berubah dan tidak berubah dalam Python sangat penting. Hindari perilaku aneh yang dijelaskan dalam artikel ini. Terutama:
- .
- - .