Bisakah Anda memecahkan tiga masalah sederhana ini dengan Python?

Sejak awal perjalanan saya sebagai pengembang perangkat lunak, saya sangat suka mempelajari bagian dalam bahasa pemrograman. Saya selalu tertarik pada bagaimana konstruksi ini atau itu bekerja, bagaimana tim ini atau itu bekerja, apa yang ada di balik tudung gula sintaksis, dll. Baru-baru ini, saya menemukan sebuah artikel yang menarik dengan contoh-contoh bagaimana benda-benda yang bisa berubah dan berubah-ubah dalam Python tidak selalu jelas berfungsi. Menurut pendapat saya, kuncinya adalah bagaimana perilaku kode berubah tergantung pada tipe data yang digunakan, sambil mempertahankan semantik identik dan konstruksi bahasa yang digunakan. Ini adalah contoh pemikiran yang bagus tidak hanya saat menulis, tetapi juga saat menggunakan. Saya mengundang semua orang untuk membiasakan diri dengan terjemahan.







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 ldan 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 xdiubah, operasi x+=5tidak 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 7ditambahkan ke set awalnya kosong dan ternyata {7}; maka nilai tersebut 6ditambahkan ke set {4, 5}dan diperoleh {4, 5, 6}.



Dan kemudian hal-hal aneh dimulai. Nilai 2ditambahkan bukan ke set kosong, tetapi ke {7}. Mengapa? Nilai awal dari parameter opsional sdihitung 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 samasyang 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 lmasih merujuk ke alamat lama di memori. Namun, mencoba mengubah variabel yang tidak dapat diubah dalam fungsi kedua y += xmenghasilkan 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:



  • .
  • - .



All Articles