Chapter Scope dan Namespace menetapkan aturan default: assignment di dalam badan function selalu membuat nama baru di scope lokal. Tetapi bagaimana jika kita sungguh-sungguh perlu memperbarui variabel global, atau variabel milik function pembungkus? Python menyediakan dua kata kunci untuk itu: global dan nonlocal. Keduanya berguna untuk dipahami — meski di kebanyakan kode sehari-hari justru paling aman tidak memakainya. Chapter ini membahas keduanya, galat klasik yang muncul tanpa mereka, dan pola alternatif yang lebih sehat.
Kesalahan Umum: UnboundLocalError #
Kode berikut terlihat wajar: function membaca saldo global, menguranginya, lalu menyimpan hasilnya kembali. Kenyataannya, function ini meledak:
>>> saldo = 50000
>>> def tarik_tunai(nominal):
... saldo = saldo - nominal
... return saldo
...
>>> tarik_tunai(10000)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in tarik_tunai
UnboundLocalError: cannot access local variable 'saldo' where it is not associated with a valuePenyebabnya halus namun tegas. Karena baris saldo = saldo - nominal melakukan assignment terhadap saldo di dalam badan function, interpreter Python memutuskan sejak kompilasi bahwa saldo adalah nama lokal untuk seluruh function — termasuk pada sisi kanan tanda sama dengan. Saat eksekusi mencapai baris tersebut, sisi kanan membutuhkan nilai saldo lokal, padahal nama itu belum pernah diberi nilai. Terjadilah UnboundLocalError.
Perhatikan pula struktur traceback di atas: ada dua baris File — baris pertama menunjuk pemanggilan di baris terluar, baris kedua menunjuk baris ke-2 di dalam tarik_tunai tempat galat terjadi. Membaca traceback dari bawah ke atas mempercepat pencarian akar masalah.
Keyword global #
Kata kunci global ditempatkan di awal badan function untuk menyatakan: nama tertentu mengacu ke namespace global, sehingga assignment di dalam function mengubah object yang dirujuk nama global tersebut:
skor = 0
def tambah_skor(poin):
global skor
skor = skor + poin
tambah_skor(10)
tambah_skor(25)
print(skor)Output yang Dihasilkan:
35Mari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 1 (
skor = 0): namaskordibuat di scope global dengan nilai0. - Baris 4 (
global skor): statement ini menghapusskordari daftar nama lokal function. Sejak baris ini, setiapskordi badan function mengacu ke variabel global — baik untuk dibaca maupun untuk ditulis. - Baris 5 (
skor = skor + poin): nilai global0dibaca, dijumlahkan10, hasilnya ditulis kembali ke variabel global. Pemanggilan kedua mengulang proses dengan25, menghasilkan35.
Keyword nonlocal untuk Enclosing Scope #
nonlocal berlaku untuk function bersarang: kata kunci ini mengikat nama ke variabel milik function pembungkus (lapisan E pada aturan LEGB), bukan yang paling global:
def buat_penghitung():
hitungan = 0
def naik():
nonlocal hitungan
hitungan = hitungan + 1
return hitungan
return naik
penghitung = buat_penghitung()
print(penghitung())
print(penghitung())
print(penghitung())Output yang Dihasilkan:
1
2
3Mari kita telaah alurnya:
- Baris 2 (
hitungan = 0): variabelhitunganhidup di namespace lokal milikbuat_penghitung— function pembungkus. - Baris 5 (
nonlocal hitungan): di dalamnaik, namahitungandiikat ke variabel enclosing tersebut. Tanpa baris ini, assignment di baris 6 akan membuat variabel lokal baru di dalamnaik(dan memicuUnboundLocalErrorseperti kasussaldotadi). - Baris 10 (
return naik): functionbuat_penghitungmengembalikan functionnaikitu sendiri — bukan hasil pemanggilannya. Variabelhitungantetap hidup karenanaikmasih memegang akses ke namespace pembungkusnya. - Baris 13-15: setiap pemanggilan
penghitung()menaikkanhitunganyang sama, menghasilkan1,2,3.
Pola function yang “membawa data” seperti ini disebut closure — fondasi penting sebelum melanjutkan ke Section Decorators dan Generators.
Praktik Terbaik: Lewat Parameter, Kembalikan Hasil #
Kedua kata kunci di atas bisa dihindari pada mayoritas kasus dengan pola yang sudah kita kuasai: kirim data lewat parameter, terima hasil lewat return. Tarik tunai versi sehat:
def tarik_saldo(saldo, nominal):
return saldo - nominal
saldo = 50000
saldo = tarik_saldo(saldo, 10000)
saldo = tarik_saldo(saldo, 5000)
print(saldo)Output yang Dihasilkan:
35000Dependensi function kini terbaca penuh dari signature: tarik_saldo menerima saldo, mengembalikan saldo baru, dan pemanggil yang memutuskan menyimpannya. Tidak ada state tersembunyi, tidak ada urutan pemanggilan yang harus dijaga di kepala, dan setiap function bisa diuji sendirian tanpa menyiapkan variabel global lebih dulu.