Kode berikut berfungsi seperti yang diharapkan di Python 2.5 dan 3.0:
a, b, c = (1, 2, 3)
print(a, b, c)
def test():
print(a)
print(b)
print(c) # (A)
#c+=1 # (B)
test()
Namun, ketika saya batalkan komentar pada baris (B) , saya mendapat UnboundLocalError: 'c' not assigned
at at line (A) . Nilai-nilai a
dan b
dicetak dengan benar. Ini membuat saya benar-benar bingung karena dua alasan:
Mengapa ada kesalahan runtime yang dilemparkan pada baris (A) karena pernyataan selanjutnya pada baris (B) ?
Mengapa variabel
a
danb
dicetak seperti yang diharapkan, sementarac
menimbulkan kesalahan?
Satu-satunya penjelasan yang dapat saya kemukakan adalah bahwa variabel lokalc
dibuat oleh penugasan c+=1
, yang lebih diutamakan daripada variabel "global" c
bahkan sebelum variabel lokal dibuat. Tentu saja, tidak masuk akal jika variabel mencuri "ruang lingkup" sebelum ada.
Bisakah seseorang tolong jelaskan perilaku ini?
Jawaban:
Python memperlakukan variabel dalam fungsi berbeda tergantung pada apakah Anda menetapkan nilai kepada mereka dari dalam atau di luar fungsi. Jika variabel ditugaskan dalam suatu fungsi, itu diperlakukan secara default sebagai variabel lokal. Oleh karena itu, ketika Anda menghapus tanda komentar pada baris Anda mencoba untuk referensi variabel lokal
c
sebelum nilai apa pun ditugaskan untuk itu.Jika Anda ingin variabel
c
merujuk ke global yangc = 3
ditugaskan sebelum fungsi, masukkansebagai baris pertama dari fungsi.
Adapun python 3, sekarang ada
yang bisa Anda gunakan untuk merujuk ke ruang lingkup fungsi melampirkan terdekat yang memiliki
c
variabel.sumber
global
ataunonlocal
untuk memaksa penugasan global atau non-lokal)Python sedikit aneh karena menyimpan semuanya dalam kamus untuk berbagai cakupan. Aslinya a, b, c berada dalam ruang lingkup teratas dan dalam kamus paling atas itu. Fungsi memiliki kamus sendiri. Saat Anda mencapai
print(a)
danprint(b)
pernyataan, tidak ada yang namanya dalam kamus, jadi Python mencari daftar dan menemukan mereka di kamus global.Sekarang kita bisa
c+=1
, yang, tentu saja, setara denganc=c+1
. Ketika Python memindai baris itu, dikatakan "aha, ada variabel bernama c, saya akan memasukkannya ke dalam kamus lingkup lokal saya." Kemudian ketika ia pergi mencari nilai untuk c untuk c di sisi kanan penugasan, ia menemukan variabel lokal bernama c , yang belum memiliki nilai, dan melempar kesalahan.Pernyataan yang
global c
disebutkan di atas hanya memberitahu parser bahwa ia menggunakanc
dari lingkup global sehingga tidak perlu yang baru.Alasannya mengatakan ada masalah pada baris yang dilakukannya adalah karena secara efektif mencari nama-nama sebelum mencoba untuk menghasilkan kode, dan dalam beberapa hal tidak berpikir itu benar-benar melakukan garis itu. Saya berpendapat bahwa ini adalah bug kegunaan, tetapi umumnya merupakan praktik yang baik untuk hanya belajar untuk tidak menganggap pesan kompiler terlalu serius.
Jika itu suatu kenyamanan, saya mungkin menghabiskan sehari menggali dan bereksperimen dengan masalah yang sama ini sebelum saya menemukan sesuatu yang ditulis Guido tentang kamus yang Menjelaskan Segalanya.
Perbarui, lihat komentar:
Itu tidak memindai kode dua kali, tetapi memindai kode dalam dua fase, lexing dan parsing.
Pertimbangkan bagaimana penguraian baris kode ini bekerja. Lexer membaca teks sumber dan memecahnya menjadi leksem, "komponen terkecil" dari tata bahasa. Jadi ketika itu menyentuh garis
itu memecahnya menjadi sesuatu seperti
Pengurai akhirnya ingin menjadikan ini menjadi pohon parse dan menjalankannya, tetapi karena ini adalah tugas, sebelum itu, ia mencari nama c dalam kamus lokal, tidak melihatnya, dan memasukkannya ke dalam kamus, menandai sebagai tidak diinisialisasi. Dalam bahasa yang dikompilasi penuh, itu hanya akan pergi ke tabel simbol dan menunggu parse, tetapi karena TIDAK AKAN memiliki kemewahan pass kedua, lexer melakukan sedikit pekerjaan ekstra untuk membuat hidup lebih mudah di kemudian hari. Hanya saja, kemudian ia melihat OPERATOR, melihat bahwa aturan mengatakan "jika Anda memiliki operator + = sisi kiri pasti telah diinisialisasi" dan mengatakan "whoops!"
Intinya di sini adalah bahwa itu belum benar-benar memulai parse dari garis . Ini semua terjadi semacam persiapan untuk parse yang sebenarnya, sehingga penghitung baris belum maju ke baris berikutnya. Jadi ketika sinyal kesalahan, ia masih berpikir itu pada baris sebelumnya.
Seperti yang saya katakan, Anda bisa berpendapat itu adalah bug kegunaan, tetapi sebenarnya hal yang cukup umum. Beberapa kompiler lebih jujur tentang hal itu dan mengatakan "kesalahan pada atau di sekitar jalur XXX", tetapi yang ini tidak.
sumber
dict
, itu secara internal hanya sebuah array (locals()
akan mengisi adict
untuk kembali, tetapi perubahan itu tidak membuat yang barulocals
). Fase parse menemukan setiap penugasan ke lokal dan mengkonversi dari nama ke posisi dalam array itu, dan menggunakan posisi itu setiap kali nama direferensikan. Saat masuk ke fungsi, penduduk lokal non-argumen diinisialisasi ke placeholder, danUnboundLocalError
terjadi ketika variabel dibaca dan indeks terkait masih memiliki nilai placeholder.Melihat pembongkaran dapat mengklarifikasi apa yang terjadi:
Seperti yang Anda lihat, bytecode untuk mengakses a adalah
LOAD_FAST
, dan untuk bLOAD_GLOBAL
,. Ini karena kompiler telah mengidentifikasi bahwa ditugaskan ke dalam fungsi, dan mengklasifikasikannya sebagai variabel lokal. Mekanisme akses untuk penduduk lokal pada dasarnya berbeda untuk global - mereka secara statis ditugaskan offset dalam tabel variabel frame, yang berarti pencarian adalah indeks cepat, daripada pencarian dict lebih mahal untuk global. Karena itu, Python membacaprint a
baris sebagai "dapatkan nilai variabel lokal 'a' yang disimpan di slot 0, dan cetaklah", dan ketika mendeteksi bahwa variabel ini masih belum diinisialisasi, menimbulkan pengecualian.sumber
Python memiliki perilaku yang agak menarik ketika Anda mencoba semantik variabel global tradisional. Saya tidak ingat detailnya, tetapi Anda dapat membaca nilai variabel yang dideklarasikan dalam lingkup 'global' baik-baik saja, tetapi jika Anda ingin memodifikasinya, Anda harus menggunakan
global
kata kunci. Coba ubahtest()
ke ini:Selain itu, alasan Anda mendapatkan kesalahan ini adalah karena Anda juga dapat mendeklarasikan variabel baru di dalam fungsi itu dengan nama yang sama dengan yang 'global', dan itu akan benar-benar terpisah. Penerjemah mengira Anda mencoba membuat variabel baru dalam lingkup ini yang disebut
c
dan memodifikasi semuanya dalam satu operasi, yang tidak diizinkan dalam Python karena ini baruc
tidak diinisialisasi.sumber
Contoh terbaik yang membuatnya jelas adalah:
saat memanggil
foo()
, ini juga memunculkanUnboundLocalError
meskipun kita tidak akan pernah mencapai garisbar=0
, jadi secara logis variabel lokal tidak boleh dibuat.Misteri terletak pada " Python is an Interpreted Language " dan deklarasi fungsi
foo
ditafsirkan sebagai pernyataan tunggal (yaitu pernyataan majemuk), itu hanya menafsirkannya dengan bodoh dan menciptakan lingkup lokal dan global. Jadibar
diakui dalam lingkup lokal sebelum eksekusi.Untuk lebih banyak contoh seperti ini Baca posting ini: http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/
Posting ini memberikan Deskripsi dan Analisis Lengkap tentang Pelingkupan Python variabel:
sumber
Berikut adalah dua tautan yang dapat membantu
1: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value
2: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-reference
tautan satu menjelaskan kesalahan UnboundLocalError. Tautan dua dapat membantu menulis ulang fungsi pengujian Anda. Berdasarkan tautan dua, masalah asli dapat ditulis ulang sebagai:
sumber
Ini bukan jawaban langsung untuk pertanyaan Anda, tetapi terkait erat, karena ini adalah masalah lain yang disebabkan oleh hubungan antara penugasan yang diperbesar dan lingkup fungsi.
Dalam kebanyakan kasus, Anda cenderung menganggap penugasan yang diperbesar (
a += b
) sama persis dengan penugasan sederhana (a = a + b
). Dimungkinkan untuk mendapatkan masalah dengan ini, dalam satu kasus sudut. Biarkan saya jelaskan:Cara penugasan sederhana Python bekerja berarti bahwa jika
a
dilewatkan ke fungsi (sepertifunc(a)
; perhatikan bahwa Python selalu lulus-oleh-referensi), makaa = a + b
tidak akan mengubaha
yang dilewatkan. Sebaliknya, itu hanya akan mengubah pointer lokal kea
.Tetapi jika Anda menggunakan
a += b
, maka kadang-kadang diimplementasikan sebagai:atau kadang-kadang (jika metode itu ada) sebagai:
Dalam kasus pertama (selama
a
tidak dinyatakan global), tidak ada efek samping di luar cakupan lokal, karena penugasana
hanyalah pembaruan pointer.Dalam kasus kedua,
a
sebenarnya akan memodifikasi sendiri, sehingga semua referensia
akan menunjuk ke versi yang dimodifikasi. Ini ditunjukkan oleh kode berikut:Jadi triknya adalah untuk menghindari penugasan augmented pada argumen fungsi (saya mencoba hanya menggunakannya untuk variabel lokal / loop). Gunakan tugas yang sederhana, dan Anda akan aman dari perilaku yang ambigu.
sumber
Penerjemah Python akan membaca fungsi sebagai unit yang lengkap. Saya menganggapnya sebagai membacanya dalam dua lintasan, sekali untuk mengumpulkan penutupannya (variabel lokal), kemudian mengubahnya menjadi byte-code.
Karena saya yakin Anda sudah mengetahui, nama apa pun yang digunakan di sebelah kiri '=' secara implisit adalah variabel lokal. Lebih dari sekali saya tertangkap dengan mengubah akses variabel ke + = dan tiba-tiba menjadi variabel yang berbeda.
Saya juga ingin menunjukkan bahwa itu tidak benar-benar ada hubungannya dengan ruang lingkup global secara khusus. Anda mendapatkan perilaku yang sama dengan fungsi bersarang.
sumber
c+=1
wakilnyac
, python mengasumsikan ditugaskan variabel lokal, tetapi dalam kasus ini belum dinyatakan secara lokal.Baik menggunakan kata kunci
global
ataunonlocal
.nonlocal
hanya berfungsi di python 3, jadi jika Anda menggunakan python 2 dan tidak ingin membuat variabel Anda global, Anda bisa menggunakan objek yang bisa diubah:sumber
Cara terbaik untuk mencapai variabel kelas adalah akses langsung dengan nama kelas
sumber
Dalam python kita memiliki deklarasi yang sama untuk semua jenis variabel lokal, variabel kelas dan variabel global. ketika Anda merujuk variabel global dari metode, python berpikir bahwa Anda sebenarnya merujuk variabel dari metode itu sendiri yang belum didefinisikan dan karenanya melempar kesalahan. Untuk merujuk variabel global kita harus menggunakan global () ['variabelName'].
dalam kasus Anda gunakan global () ['a], global () [' b '] dan global () [' c '] alih-alih a, b dan c.
sumber
Masalah yang sama menggangguku. Menggunakan
nonlocal
danglobal
dapat memecahkan masalah.Namun, perhatian yang diperlukan untuk penggunaan
nonlocal
, itu berfungsi untuk fungsi bersarang. Namun, di tingkat modul, itu tidak berfungsi. Lihat contoh di sini.sumber