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:

python
>>> 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 value

Penyebabnya 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:

python
skor = 0

def tambah_skor(poin):
    global skor
    skor = skor + poin

tambah_skor(10)
tambah_skor(25)
print(skor)

Output yang Dihasilkan:

text
35

Mari kita telaah apa yang terjadi pada baris-baris kode di atas:

  1. Baris 1 (skor = 0): nama skor dibuat di scope global dengan nilai 0.
  2. Baris 4 (global skor): statement ini menghapus skor dari daftar nama lokal function. Sejak baris ini, setiap skor di badan function mengacu ke variabel global — baik untuk dibaca maupun untuk ditulis.
  3. Baris 5 (skor = skor + poin): nilai global 0 dibaca, dijumlahkan 10, hasilnya ditulis kembali ke variabel global. Pemanggilan kedua mengulang proses dengan 25, menghasilkan 35.

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:

python
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:

text
1
2
3

Mari kita telaah alurnya:

  1. Baris 2 (hitungan = 0): variabel hitungan hidup di namespace lokal milik buat_penghitung — function pembungkus.
  2. Baris 5 (nonlocal hitungan): di dalam naik, nama hitungan diikat ke variabel enclosing tersebut. Tanpa baris ini, assignment di baris 6 akan membuat variabel lokal baru di dalam naik (dan memicu UnboundLocalError seperti kasus saldo tadi).
  3. Baris 10 (return naik): function buat_penghitung mengembalikan function naik itu sendiri — bukan hasil pemanggilannya. Variabel hitungan tetap hidup karena naik masih memegang akses ke namespace pembungkusnya.
  4. Baris 13-15: setiap pemanggilan penghitung() menaikkan hitungan yang sama, menghasilkan 1, 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:

python
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:

text
35000

Dependensi 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.