Python & AI Tutorials Logo
Pemrograman Python

21. Cakupan Variabel dan Resolusi Nama

Saat kamu membuat variabel di Python, di mana variabel itu “tinggal”? Apakah sebuah fungsi(function) bisa melihat variabel yang dibuat di luar dirinya? Apakah kode di luar sebuah fungsi bisa mengakses variabel yang dibuat di dalamnya? Pertanyaan-pertanyaan ini berkaitan dengan cakupan(scope) — wilayah di program kamu tempat sebuah nama terlihat dan bisa digunakan.

Memahami cakupan itu krusial untuk menulis fungsi(function) yang bekerja dengan benar dan bisa diprediksi. Tanpa pengetahuan ini, kamu bisa tidak sengaja membuat bug ketika variabel tidak memiliki nilai seperti yang kamu harapkan, atau ketika perubahan pada variabel tidak bertahan seperti yang dimaksud.

Di bab ini, kita akan mengeksplorasi bagaimana Python menentukan variabel mana yang dirujuk oleh sebuah nama, bagaimana mengontrol di mana variabel bisa diakses, dan apa yang terjadi ketika kamu menghapus sebuah nama. Di akhir, kamu akan memahami aturan yang mengatur visibilitas variabel di program Python.

21.1) Variabel Lokal dan Global

Setiap variabel di Python berada dalam sebuah cakupan(scope) tertentu — sebuah wilayah kode tempat nama variabel itu didefinisikan dan bisa diakses. Dua cakupan(scope) yang paling mendasar adalah lokal dan global.

Memahami Cakupan Global

Variabel yang dibuat di level paling atas program kamu — di luar fungsi(function) mana pun — berada di cakupan global. Ini disebut variabel global, dan bisa diakses dari mana saja di modul kamu setelah didefinisikan.

python
# Variabel global - didefinisikan di level modul
total_users = 0
 
def show_user_count():
    # Fungsi ini bisa MEMBACA variabel global
    print(f"Total users: {total_users}")
 
show_user_count()  # Output: Total users: 0
print(total_users)  # Output: 0

Dalam contoh ini, total_users adalah variabel global. Baik fungsi show_user_count() maupun kode di level modul bisa mengaksesnya. Anggap saja variabel global terlihat di seluruh file program kamu.

Memahami Cakupan Lokal

Variabel yang dibuat di dalam sebuah fungsi(function) berada di cakupan lokal fungsi tersebut. Ini disebut variabel lokal, dan hanya bisa diakses di dalam fungsi tempat variabel itu didefinisikan. Begitu fungsi selesai dieksekusi, variabel lokal menghilang.

python
def calculate_discount(price):
    # discount_rate adalah LOKAL untuk fungsi ini
    discount_rate = 0.15
    discount_amount = price * discount_rate
    return discount_amount
 
result = calculate_discount(100)
print(result)  # Output: 15.0
 
# Ini akan menyebabkan error - discount_rate tidak ada di sini
# print(discount_rate)  # NameError: name 'discount_rate' is not defined

Variabel discount_rate dan discount_amount hanya ada saat calculate_discount() sedang berjalan. Setelah fungsi mengembalikan nilai, nama-nama ini tidak ada lagi. Ini justru hal yang baik — ini mencegah fungsi memenuhi program kamu dengan variabel sementara.

Kenapa Cakupan Lokal Itu Penting

Cakupan lokal menyediakan enkapsulasi(encapsulation) — setiap fungsi(function) punya ruang kerja privatnya sendiri. Ini berarti kamu bisa menggunakan nama variabel yang sama di fungsi yang berbeda tanpa konflik:

python
def calculate_tax(amount):
    rate = 0.08  # Variabel lokal
    return amount * rate
 
def calculate_shipping(weight):
    rate = 5.00  # Variabel lokal berbeda dengan nama yang sama
    return weight * rate
 
tax = calculate_tax(100)
shipping = calculate_shipping(3)
 
print(f"Tax: ${tax}")         # Output: Tax: $8.0
print(f"Shipping: ${shipping}")  # Output: Shipping: $15.0

Kedua fungsi menggunakan variabel bernama rate, tetapi itu benar-benar variabel yang terpisah di cakupan lokal yang berbeda. Perubahan pada rate di satu fungsi tidak memengaruhi rate di fungsi lainnya. Isolasi ini membuat fungsi lebih andal dan lebih mudah dipahami.

Membaca Variabel Global dari Dalam Fungsi

Fungsi(function) bisa membaca variabel global tanpa sintaks khusus apa pun:

python
# Konfigurasi global
max_login_attempts = 3
 
def check_login(password):
    # Membaca variabel global
    if password == "secret123":
        return "Login successful"
    else:
        return f"Invalid password. You have {max_login_attempts} attempts."
 
result = check_login("wrong")
print(result)  # Output: Invalid password. You have 3 attempts.

Fungsi check_login() bisa membaca max_login_attempts karena itu variabel global. Namun, ada batasan penting yang perlu kita pahami.

Aturan “Assignment Membuat Variabel Lokal”

Di sinilah cakupan(scope) jadi rumit. Jika kamu melakukan assignment ke sebuah nama variabel di dalam fungsi, Python membuat variabel lokal baru dengan nama itu, bahkan jika ada variabel global dengan nama yang sama:

python
counter = 0  # Variabel global
 
def increment_counter():
    # PERINGATAN: Ini membuat variabel lokal BARU bernama counter - hanya untuk demonstrasi
    # MASALAH: Mencoba membaca counter sebelum memberinya nilai secara lokal
    counter = counter + 1  # UnboundLocalError: local variable 'counter' referenced before assignment
    print(counter)
 
# increment_counter() # Pemanggilan ini menghasilkan UnboundLocalError

Kode ini gagal karena Python melihat assignment counter = counter + 1 dan memutuskan bahwa counter harus menjadi variabel lokal. Namun ketika Python mencoba mengevaluasi counter + 1, variabel lokal counter belum punya nilai — kita mencoba memakainya sebelum meng-assign nilainya.

Ini adalah sumber kebingungan yang umum. Aturannya: jika sebuah fungsi melakukan assignment ke sebuah nama variabel di mana pun di dalam body-nya, nama itu diperlakukan sebagai lokal di seluruh fungsi, bahkan sebelum assignment.

Mari lihat ini dengan lebih jelas:

python
message = "Hello"  # Variabel global
 
def show_message():
    print(message)  # Ini bekerja - hanya membaca global
    
def change_message():
    # PERINGATAN: Ini mendemonstrasikan error yang umum - hanya untuk demonstrasi
    # MASALAH: Python melihat assignment di bawah, jadi message diperlakukan sebagai lokal di seluruh fungsi
    print(message)  # UnboundLocalError!
    message = "Goodbye"  # Ini membuat message menjadi lokal untuk SELURUH fungsi
 
show_message()  # Output: Hello
# change_message()  # Pemanggilan ini menghasilkan UnboundLocalError

Fungsi show_message() bekerja baik karena hanya membaca message. Tetapi change_message() gagal karena assignment pada baris kedua membuat Python memperlakukan message sebagai lokal di seluruh fungsi, termasuk pada pernyataan print() yang muncul sebelum assignment.

Parameter Adalah Variabel Lokal

Parameter fungsi(function) adalah variabel lokal yang mendapatkan nilai awal dari argumen yang diberikan saat fungsi dipanggil:

python
def greet(name):  # 'name' adalah variabel lokal
    greeting = f"Hello, {name}!"  # 'greeting' juga lokal
    return greeting
 
message = greet("Alice")
print(message)  # Output: Hello, Alice!
 
# Baik 'name' maupun 'greeting' tidak ada di sini
# print(name)  # NameError

Parameter name hanya ada di dalam fungsi greet(). Ia dibuat ketika fungsi dipanggil dan menghilang ketika fungsi mengembalikan nilai.

Contoh Praktis: Perhitungan Keranjang Belanja

Mari lihat bagaimana cakupan lokal dan global bekerja bersama dalam skenario yang realistis:

python
# Konfigurasi global
tax_rate = 0.08
free_shipping_threshold = 50
 
def calculate_total(subtotal):
    # Variabel lokal untuk perhitungan ini
    tax = subtotal * tax_rate  # Membaca tax_rate global
    
    # Tentukan biaya pengiriman
    if subtotal >= free_shipping_threshold:  # Membaca threshold global
        shipping = 0
    else:
        shipping = 5.99
    
    total = subtotal + tax + shipping
    return total
 
# Hitung untuk nilai keranjang yang berbeda
cart1 = calculate_total(30)
cart2 = calculate_total(60)
 
print(f"Cart 1 total: ${cart1:.2f}")  # Output: Cart 1 total: $38.39
print(f"Cart 2 total: ${cart2:.2f}")  # Output: Cart 2 total: $64.80

Dalam contoh ini:

  • tax_rate dan free_shipping_threshold adalah nilai konfigurasi global
  • subtotal, tax, shipping, dan total bersifat lokal untuk tiap pemanggilan calculate_total()
  • Setiap pemanggilan fungsi mendapat kumpulan variabel lokalnya sendiri yang terpisah
  • Fungsi bisa membaca konfigurasi global tetapi tidak memodifikasinya

Pemisahan tanggung jawab ini membuat kode jelas: variabel global menyimpan konfigurasi yang berlaku di mana-mana, sementara variabel lokal menyimpan hasil perhitungan sementara yang spesifik untuk tiap pemanggilan fungsi.

21.2) Aturan LEGB untuk Resolusi Nama

Saat Python menemui sebuah nama variabel, bagaimana ia tahu variabel mana yang kamu maksud? Python mengikuti urutan pencarian khusus yang disebut aturan LEGB. LEGB adalah singkatan dari Local, Enclosing, Global, Built-in — empat cakupan(scope) yang dicari Python, dalam urutan itu.

Empat Cakupan dalam LEGB

Mari pahami tiap cakupan(scope) dalam hierarki LEGB:

  1. Local (L): Cakupan fungsi saat ini
  2. Enclosing (E): Cakupan fungsi-fungsi yang membungkus (fungsi yang berisi fungsi saat ini)
  3. Global (G): Cakupan level modul
  4. Built-in (B): Nama built-in Python seperti print, len, int, dan lain-lain

Saat kamu menggunakan sebuah nama variabel, Python mencari di cakupan-cakupan ini secara berurutan: L → E → G → B. Python memakai kecocokan pertama yang ditemukan lalu berhenti mencari.

Cakupan Lokal: Tempat Pertama yang Dicek Python

Python selalu mengecek cakupan lokal terlebih dahulu:

python
def calculate_price():
    price = 100  # Variabel lokal
    tax = 0.08   # Variabel lokal
    total = price * (1 + tax)
    return total
 
result = calculate_price()
print(result)  # Output: 108.0

Saat Python melihat price, tax, dan total di dalam calculate_price(), ia menemukannya di cakupan lokal dan menggunakan nilai tersebut. Pencarian berhenti di cakupan lokal — Python tidak perlu melihat lebih jauh.

Cakupan Global: Saat Lokal Tidak Memilikinya

Jika sebuah nama tidak ditemukan secara lokal, Python mengecek cakupan global:

python
# Variabel global
default_tax_rate = 0.08
default_currency = "USD"
 
def calculate_price(amount):
    # 'amount' bersifat lokal, langsung ditemukan
    # 'default_tax_rate' tidak lokal, ditemukan di cakupan global
    total = amount * (1 + default_tax_rate)
    return total
 
result = calculate_price(100)
print(result)  # Output: 108.0

Saat Python menemui default_tax_rate di dalam fungsi, ia tidak menemukannya secara lokal, jadi ia mencari di cakupan global dan menemukannya di sana.

Cakupan Built-in: Nama yang Sudah Disediakan Python

Jika sebuah nama tidak ditemukan di cakupan lokal atau global, Python mengecek cakupan built-in — nama-nama yang disediakan Python secara otomatis:

python
def process_data(numbers):
    # 'numbers' bersifat lokal
    # 'len' tidak lokal atau global - itu built-in
    count = len(numbers)
    
    # 'max' juga built-in
    maximum = max(numbers)
    
    return count, maximum
 
data = [10, 25, 15, 30, 20]
result = process_data(data)
print(result)  # Output: (5, 30)

Nama len dan max tidak didefinisikan di kode kamu — itu adalah fungsi built-in yang disediakan Python. Saat Python tidak menemukan nama-nama ini secara lokal atau global, ia mengecek cakupan built-in dan menemukannya di sana.

Cakupan Enclosing: Fungsi Bersarang

Cakupan enclosing berperan saat kamu punya fungsi bersarang — fungsi(function) yang didefinisikan di dalam fungsi lain. Di sinilah “E” pada LEGB menjadi penting:

python
def outer_function():
    outer_var = "I'm from outer"  # Di cakupan enclosing untuk inner_function
    
    def inner_function():
        inner_var = "I'm from inner"  # Lokal untuk inner_function
        # inner_function bisa melihat inner_var (lokal) dan outer_var (enclosing)
        print(inner_var)   # Output: I'm from inner
        print(outer_var)   # Output: I'm from outer
    
    inner_function()
 
outer_function()

Untuk inner_function(), cakupan(scope) milik outer_function() adalah sebuah cakupan enclosing. Saat inner_function() merujuk outer_var, Python mencari:

  1. Cakupan lokal inner_function() — tidak ditemukan
  2. Cakupan enclosing outer_function() — ditemukan! Nilai ini yang digunakan

LEGB dalam Aksi: Contoh Sederhana

Mari lihat keempat cakupan bekerja bersama dalam contoh yang jelas dan sederhana:

python
# Built-in: len (Python menyediakannya)
# Global: multiplier
multiplier = 10
 
def outer(x):
    # Cakupan enclosing untuk inner
    y = 5
    
    def inner(z):
        # Cakupan lokal
        # z bersifat lokal (L)
        # y berasal dari cakupan enclosing (E)
        # multiplier berasal dari cakupan global (G)
        # len berasal dari cakupan built-in (B)
        result = len([z, y, multiplier])  # Menggunakan keempat cakupan!
        return z + y + multiplier
 
    return inner(3)
 
answer = outer(100)
print(answer)  # Output: 18

Saat Python mengevaluasi z + y + multiplier di dalam inner():

  1. L (Local): Menemukan z = 3
  2. E (Enclosing): Menemukan y = 5 di outer()
  3. G (Global): Menemukan multiplier = 10
  4. B (Built-in): Menemukan fungsi len

Contoh ini dengan jelas mendemonstrasikan bagaimana Python mencari melalui keempat cakupan untuk melakukan resolusi nama.

Shadowing: Saat Cakupan Dalam Menutupi Nama Luar

Jika nama yang sama ada di beberapa cakupan, cakupan yang paling dalam “menang” — ini disebut shadowing:

python
value = "global"
 
def outer():
    value = "enclosing"
    
    def inner():
        value = "local"
        print(value)  # value yang mana?
    
    inner()
    print(value)  # value yang mana?
 
outer()
print(value)  # value yang mana?

Output:

local
enclosing
global

Setiap pernyataan print() melihat value yang berbeda karena Python berhenti pada kecocokan pertama:

  • Di dalam inner(): menemukan value secara lokal → mencetak "local"
  • Di dalam outer() tetapi di luar inner(): menemukan value di cakupan outer() → mencetak "enclosing"
  • Di level modul: menemukan value secara global → mencetak "global"

Memvisualisasikan Urutan Pencarian LEGB

Ya

Tidak

Ya

Tidak

Ya

Tidak

Ya

Tidak

Referensi Nama

Ditemukan di Lokal?

Gunakan Nilai Lokal

Ditemukan di Enclosing?

Gunakan Nilai Enclosing

Ditemukan di Global?

Gunakan Nilai Global

Ditemukan di Built-in?

Gunakan Nilai Built-in

NameError

Diagram ini menunjukkan proses pencarian Python. Python mulai dari cakupan paling dalam lalu bergerak ke luar. Jika nama tidak ditemukan di cakupan mana pun, Python melempar NameError.

Kenapa LEGB Penting untuk Menulis Fungsi

Memahami LEGB membantu kamu:

  1. Memprediksi nilai variabel: Kamu tahu persis variabel mana yang akan digunakan Python
  2. Menghindari konflik penamaan: Kamu paham kapan sebuah nama menutupi nama lain
  3. Merancang fungsi yang lebih baik: Kamu bisa memutuskan cakupan mana yang tepat untuk tiap variabel
  4. Debug masalah cakupan: Saat variabel tidak punya nilai seperti yang diharapkan, kamu bisa melacak lewat LEGB

Aturan LEGB adalah fondasi cara Python melakukan resolusi nama. Setiap kali kamu menggunakan variabel, Python mengikuti aturan ini di balik layar.

21.3) Menggunakan Keyword global dengan Hati-hati

Kita sudah melihat bahwa fungsi(function) dapat membaca variabel global, tetapi bagaimana jika kamu perlu memodifikasi variabel global dari dalam sebuah fungsi? Di sinilah keyword global berperan — tetapi sebaiknya dipakai hemat dan dengan hati-hati.

Masalahnya: Assignment Membuat Variabel Lokal

Seperti yang kita pelajari sebelumnya, assignment ke variabel di dalam fungsi akan membuat variabel lokal:

python
counter = 0  # Variabel global
 
def increment():
    # PERINGATAN: Ini membuat variabel lokal BARU bernama counter - hanya untuk demonstrasi
    # MASALAH: Mencoba membaca counter sebelum memberinya nilai secara lokal
    counter = counter + 1  # UnboundLocalError!
    
# increment()  # Pemanggilan ini menghasilkan UnboundLocalError

Ini gagal karena Python melihat assignment dan memperlakukan counter sebagai lokal di seluruh fungsi. Namun kita mencoba membaca counter sebelum memberinya nilai secara lokal.

Ini adalah salah satu error paling umum saat bekerja dengan variabel global. Pesan error UnboundLocalError: local variable 'counter' referenced before assignment memberi tahu kamu persis apa yang terjadi: Python memutuskan counter adalah lokal (karena ada assignment), tetapi kamu mencoba menggunakannya sebelum memberinya nilai.

Solusinya: Mendeklarasikan Variabel sebagai Global

Keyword global memberi tahu Python: “Jangan buat variabel lokal baru dengan nama ini. Gunakan variabel globalnya.”

python
counter = 0  # Variabel global
 
def increment():
    global counter  # Beri tahu Python untuk menggunakan counter global
    counter = counter + 1  # Sekarang ini memodifikasi variabel global
 
print(f"Before: {counter}")  # Output: Before: 0
increment()
print(f"After: {counter}")   # Output: After: 1
increment()
print(f"After again: {counter}")  # Output: After again: 2

Deklarasi global counter harus muncul sebelum kamu menggunakan variabel tersebut. Itu memberi tahu Python bahwa assignment apa pun ke counter di fungsi ini harus memodifikasi variabel global, bukan membuat variabel lokal.

Beberapa Variabel Global

Kamu bisa mendeklarasikan beberapa variabel sebagai global dalam satu pernyataan:

python
total_sales = 0
total_customers = 0
 
def record_sale(amount):
    global total_sales, total_customers
    total_sales += amount
    total_customers += 1
 
print(f"Sales: ${total_sales}, Customers: {total_customers}")
# Output: Sales: $0, Customers: 0
 
record_sale(25.50)
record_sale(30.00)
 
print(f"Sales: ${total_sales}, Customers: {total_customers}")
# Output: Sales: $55.5, Customers: 2

Baik total_sales maupun total_customers dideklarasikan global, sehingga fungsi bisa memodifikasi keduanya.

Kapan Menggunakan global: Shared State

Keyword global cocok saat kamu perlu mempertahankan shared state — data yang perlu diakses dan dimodifikasi oleh beberapa fungsi:

python
# State game
player_score = 0
player_lives = 3
game_over = False
 
def award_points(points):
    global player_score
    player_score += points
    print(f"Score: {player_score}")
 
def lose_life():
    global player_lives, game_over
    player_lives -= 1
    print(f"Lives remaining: {player_lives}")
    
    if player_lives <= 0:
        game_over = True
        print("Game Over!")
 
def check_game_status():
    # Hanya membaca global - keyword global tidak diperlukan
    if game_over:
        return "Game Over"
    else:
        return f"Playing - Score: {player_score}, Lives: {player_lives}"
 
# Mainkan game
award_points(100)    # Output: Score: 100
award_points(50)     # Output: Score: 150
lose_life()          # Output: Lives remaining: 2
print(check_game_status())  # Output: Playing - Score: 150, Lives: 2

Contoh ini menunjukkan penggunaan global yang tepat: beberapa fungsi perlu memodifikasi state game yang dipakai bersama. Namun, perhatikan bahwa check_game_status() tidak membutuhkan global karena hanya membaca variabel-variabel tersebut.

Kenapa global Harus Dipakai dengan Hati-hati

Walau global kadang diperlukan, terlalu sering memakainya bisa membuat kode lebih sulit dipahami dan dirawat. Ini alasannya:

Masalah 1: Ketergantungan Tersembunyi

Saat fungsi memodifikasi variabel global, tidak jelas dari pemanggilan fungsi apa yang berubah:

python
total = 0
 
def add_to_total(value):
    global total
    total += value
 
# Fungsi ini melakukan apa? Kamu tidak bisa tahu tanpa membaca kodenya
add_to_total(10)

Bandingkan dengan fungsi yang mengembalikan nilai:

python
def add_to_total(current_total, value):
    return current_total + value
 
total = 0
total = add_to_total(total, 10)  # Jelas: total sedang diperbarui

Versi kedua membuatnya eksplisit bahwa total dimodifikasi.

Masalah 2: Testing Jadi Lebih Sulit

Fungsi yang memodifikasi state global lebih sulit diuji karena kamu perlu menyiapkan dan mereset variabel global:

python
# Sulit diuji - bergantung pada state global
score = 0
 
def add_score(points):
    global score
    score += points
 
# Setiap test perlu mereset score
# Test 1
score = 0
add_score(10)
assert score == 10
 
# Test 2 - harus mereset score lagi
score = 0
add_score(20)
assert score == 20

Masalah 3: Fungsi Tidak Reusable

Fungsi yang bergantung pada variabel global tertentu tidak mudah dipakai ulang di program lain:

python
# Fungsi ini hanya bekerja jika ada variabel global bernama 'inventory'
inventory = []
 
def add_item(item):
    global inventory
    inventory.append(item)

Alternatif yang Lebih Baik daripada global

Dalam banyak kasus, kamu bisa menghindari global dengan memakai nilai kembalian dan parameter:

Alih-alih memodifikasi state global:

python
# Menggunakan global (kurang ideal)
balance = 1000
 
def withdraw(amount):
    global balance
    if amount <= balance:
        balance -= amount
        return True
    return False
 
withdraw(100)
print(balance)  # Output: 900

Gunakan nilai kembalian:

python
# Menggunakan nilai kembalian (lebih baik)
def withdraw(balance, amount):
    if amount <= balance:
        return balance - amount, True
    return balance, False
 
balance = 1000
balance, success = withdraw(balance, 100)
print(balance)  # Output: 900

Versi kedua lebih fleksibel, mudah diuji, dan reusable.

Kapan global Memang Tepat

Ada penggunaan global yang sah:

  1. Konfigurasi yang benar-benar perlu bersifat global:
python
# Pengaturan seluruh aplikasi
debug_mode = False
log_level = "INFO"
 
def enable_debug():
    global debug_mode, log_level
    debug_mode = True
    log_level = "DEBUG"
  1. Counter untuk debugging atau statistik:
python
# Melacak pemanggilan fungsi untuk debugging
_function_call_count = 0
 
def tracked_function():
    global _function_call_count
    _function_call_count += 1
    # ... sisa fungsi

Poin Penting tentang global

  • Gunakan global hanya ketika kamu benar-benar perlu memodifikasi state level modul
  • Lebih baik mengembalikan nilai dan menggunakan parameter
  • Saat kamu memakai global, dokumentasikan kenapa itu perlu
  • Pertimbangkan apakah desain kamu bisa ditingkatkan untuk menghindari global
  • Ingat: membaca variabel global tidak memerlukan keyword global — hanya memodifikasinya yang perlu

21.4) Menggunakan nonlocal untuk Memodifikasi Variabel di Fungsi Enclosing

Saat kamu punya fungsi bersarang, kamu mungkin perlu memodifikasi variabel dari cakupan(scope) fungsi enclosing. Keyword nonlocal menyediakan tujuan ini — mirip global, tetapi untuk cakupan fungsi enclosing, bukan cakupan global.

Masalahnya: Memodifikasi Variabel Enclosing

Seperti assignment yang secara default membuat variabel lokal, masalah yang sama terjadi pada cakupan enclosing:

python
def outer():
    count = 0  # Variabel di cakupan outer
    
    def inner():
        # PERINGATAN: Ini membuat variabel lokal BARU bernama count - hanya untuk demonstrasi
        # MASALAH: Mencoba membaca count sebelum memberinya nilai secara lokal
        count = count + 1  # UnboundLocalError!
        print(count)
    
    inner()
 
# outer()  # Pemanggilan ini menghasilkan UnboundLocalError

Python melihat assignment ke count di inner() dan memperlakukannya sebagai variabel lokal. Namun kita mencoba membacanya sebelum meng-assign nilainya secara lokal, sehingga terjadi error.

Solusinya: Keyword nonlocal

Keyword nonlocal memberi tahu Python: “Variabel ini bukan lokal — cari di cakupan fungsi enclosing dan gunakan yang itu.”

python
def outer():
    count = 0  # Variabel di cakupan outer
    
    def inner():
        nonlocal count  # Gunakan count dari cakupan outer
        count = count + 1
        print(f"Count in inner: {count}")
    
    print(f"Count before: {count}")  # Output: Count before: 0
    inner()                          # Output: Count in inner: 1
    print(f"Count after: {count}")   # Output: Count after: 1
 
outer()

Sekarang inner() bisa memodifikasi variabel count dari cakupan outer(). Perubahannya bertahan setelah inner() mengembalikan nilai karena kita memodifikasi variabel sebenarnya di cakupan enclosing.

Kenapa nonlocal Berguna: Fungsi yang Mengingat State

Keyword nonlocal memungkinkan pola yang kuat, di mana fungsi inner bisa mempertahankan dan memodifikasi state dari cakupan enclosing-nya. Kita akan mempelajari closure dan factory function secara detail di Bab 23, tetapi untuk saat ini, pahami bahwa nonlocal memungkinkan fungsi inner memodifikasi variabel dari cakupan enclosing.

Berikut contoh sederhana yang menunjukkan bagaimana nonlocal bekerja:

python
def create_counter():
    count = 0  # Variabel ini ada di cakupan enclosing untuk increment
    
    def increment():
        nonlocal count  # Memodifikasi count dari cakupan enclosing
        count += 1
        return count
    
    return increment  # Kembalikan fungsi inner
 
# Buat sebuah counter
counter1 = create_counter()
 
print(counter1())  # Output: 1
print(counter1())  # Output: 2
print(counter1())  # Output: 3
 
# Buat counter independen lainnya
counter2 = create_counter()
 
print(counter2())  # Output: 1
print(counter2())  # Output: 2

Setiap pemanggilan create_counter() membuat variabel count baru dan fungsi increment() baru yang bisa memodifikasi count tersebut dengan nonlocal.

nonlocal vs global

Penting untuk memahami perbedaannya:

python
x = "global"
 
def outer():
    x = "enclosing"
    
    def use_global():
        global x  # Merujuk ke x global
        print(f"use_global sees: {x}")  # Output: use_global sees: global
    
    def use_nonlocal():
        nonlocal x  # Merujuk ke x milik outer
        print(f"use_nonlocal sees: {x}")  # Output: use_nonlocal sees: enclosing
    
    use_global()
    use_nonlocal()
 
outer()
  • global selalu merujuk ke cakupan level modul
  • nonlocal merujuk ke cakupan fungsi enclosing terdekat

Kapan Kamu Tidak Bisa Menggunakan nonlocal

Keyword nonlocal hanya bekerja dengan cakupan fungsi enclosing. Kamu tidak bisa memakainya untuk:

  1. Cakupan global (gunakan global sebagai gantinya):
python
x = "global"
 
def func():
    nonlocal x  # SyntaxError: no binding for nonlocal 'x' found
    x = "modified"
  1. Variabel yang tidak ada di cakupan enclosing mana pun:
python
def outer():
    def inner():
        nonlocal count  # SyntaxError: no binding for nonlocal 'count' found

Poin Penting tentang nonlocal

  • Gunakan nonlocal untuk memodifikasi variabel dari cakupan fungsi enclosing
  • nonlocal mencari di cakupan fungsi enclosing, bukan cakupan global
  • Membaca variabel enclosing tidak membutuhkan nonlocal — hanya memodifikasinya yang perlu
  • nonlocal memungkinkan pola yang kuat untuk membuat fungsi dengan state privat
  • Kita akan belajar lebih lanjut tentang closure dan factory function di Bab 23

Keyword nonlocal sangat berguna untuk membuat fungsi yang mempertahankan state privat, seperti yang kita lihat pada contoh counter.

21.5) Menghapus Nama (Bukan Objek) dengan del dan Artinya

Terkadang kamu perlu menghapus variabel dari namespace program kamu — mungkin untuk membebaskan memori pada program yang berjalan lama, membersihkan variabel sementara, atau menghapus entri dari koleksi. Pernyataan del di Python menangani tugas-tugas ini, tetapi penting untuk memahami dengan tepat apa yang dilakukan dan tidak dilakukan.

Pernyataan del di Python sering disalahpahami. Ia tidak menghapus objek — ia menghapus nama (binding variabel). Memahami perbedaan ini krusial untuk memahami bagaimana Python mengelola memori dan referensi.

Apa yang Sebenarnya Dilakukan del

Pernyataan del menghapus sebuah nama dari cakupan(scope) saat ini:

python
x = 42
print(x)  # Output: 42
 
del x
 
# print(x)  # NameError: name 'x' is not defined

Setelah del x, nama x tidak ada lagi di cakupan saat ini. Jika kamu mencoba memakainya, Python melempar NameError karena nama tersebut tidak didefinisikan lagi.

Menghapus Nama vs Menghapus Objek

Ini wawasan kuncinya: del menghapus nama, tidak harus menghapus objek yang dirujuk oleh nama tersebut:

python
# Buat sebuah list dan dua nama yang merujuk ke list itu
original = [1, 2, 3]
reference = original  # Kedua nama merujuk ke list yang sama
 
print(original)   # Output: [1, 2, 3]
print(reference)  # Output: [1, 2, 3]
 
# Hapus salah satu nama
del original
 
# List-nya masih ada karena 'reference' masih merujuk padanya
print(reference)  # Output: [1, 2, 3]
 
# print(original)  # NameError: name 'original' is not defined

List [1, 2, 3] tetap ada karena reference masih merujuk padanya. Menghapus original hanya menghapus nama tersebut — itu tidak menghapus objek list itu sendiri.

Kapan Objek Benar-benar Dihapus

Python secara otomatis menghapus objek ketika tidak ada lagi nama yang merujuk ke objek tersebut. Ini disebut garbage collection:

python
data = [1, 2, 3]  # List dibuat, 'data' merujuk padanya
 
del data  # Nama 'data' dihapus
 
# Sekarang list tidak punya referensi, jadi Python pada akhirnya akan menghapusnya
# (Ini terjadi otomatis - kamu tidak perlu melakukan apa pun)

Saat kita menghapus data, list [1, 2, 3] tidak punya referensi yang tersisa, sehingga garbage collector Python pada akhirnya akan mengambil kembali memorinya. Namun ini terjadi otomatis — kamu tidak mengontrol kapan tepatnya.

Menghapus Item dari Koleksi

Pernyataan del juga bisa menghapus item dari koleksi, tetapi ini secara fundamental berbeda dari menghapus nama. Saat kamu menggunakan del dengan indexing atau slicing koleksi, kamu memodifikasi koleksinya sendiri, bukan menghapus sebuah nama.

Ini perbedaan penting: saat kamu menulis del numbers[2], kamu memanggil method khusus pada objek list untuk menghapus sebuah elemen. Nama numbers masih ada dan masih merujuk ke objek list yang sama — hanya saja list-nya sekarang memiliki elemen yang lebih sedikit.

python
# Menghapus elemen list berdasarkan indeks
numbers = [10, 20, 30, 40, 50]
del numbers[2]  # Hapus elemen pada indeks 2
print(numbers)  # Output: [10, 20, 40, 50]
 
# Menghapus slice list
numbers = [10, 20, 30, 40, 50]
del numbers[1:3]  # Hapus elemen dari indeks 1 sampai 3 (eksklusif)
print(numbers)  # Output: [10, 40, 50]
 
# Menghapus entri dictionary
person = {'name': 'Alice', 'age': 30, 'city': 'Boston'}
del person['age']
print(person)  # Output: {'name': 'Alice', 'city': 'Boston'}
© 2025. Primesoft Co., Ltd.
support@primesoft.ai