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.
# 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: 0Dalam 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.
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 definedVariabel 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:
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.0Kedua 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:
# 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:
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 UnboundLocalErrorKode 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:
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 UnboundLocalErrorFungsi 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:
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) # NameErrorParameter 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:
# 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.80Dalam contoh ini:
tax_ratedanfree_shipping_thresholdadalah nilai konfigurasi globalsubtotal,tax,shipping, dantotalbersifat lokal untuk tiap pemanggilancalculate_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:
- Local (L): Cakupan fungsi saat ini
- Enclosing (E): Cakupan fungsi-fungsi yang membungkus (fungsi yang berisi fungsi saat ini)
- Global (G): Cakupan level modul
- 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:
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.0Saat 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:
# 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.0Saat 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:
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:
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:
- Cakupan lokal
inner_function()— tidak ditemukan - 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:
# 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: 18Saat Python mengevaluasi z + y + multiplier di dalam inner():
- L (Local): Menemukan
z = 3 - E (Enclosing): Menemukan
y = 5diouter() - G (Global): Menemukan
multiplier = 10 - 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:
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
globalSetiap pernyataan print() melihat value yang berbeda karena Python berhenti pada kecocokan pertama:
- Di dalam
inner(): menemukanvaluesecara lokal → mencetak "local" - Di dalam
outer()tetapi di luarinner(): menemukanvaluedi cakupanouter()→ mencetak "enclosing" - Di level modul: menemukan
valuesecara global → mencetak "global"
Memvisualisasikan Urutan Pencarian LEGB
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:
- Memprediksi nilai variabel: Kamu tahu persis variabel mana yang akan digunakan Python
- Menghindari konflik penamaan: Kamu paham kapan sebuah nama menutupi nama lain
- Merancang fungsi yang lebih baik: Kamu bisa memutuskan cakupan mana yang tepat untuk tiap variabel
- 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:
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 UnboundLocalErrorIni 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.”
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: 2Deklarasi 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:
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: 2Baik 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:
# 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: 2Contoh 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:
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:
def add_to_total(current_total, value):
return current_total + value
total = 0
total = add_to_total(total, 10) # Jelas: total sedang diperbaruiVersi 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:
# 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 == 20Masalah 3: Fungsi Tidak Reusable
Fungsi yang bergantung pada variabel global tertentu tidak mudah dipakai ulang di program lain:
# 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:
# 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: 900Gunakan nilai kembalian:
# 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: 900Versi kedua lebih fleksibel, mudah diuji, dan reusable.
Kapan global Memang Tepat
Ada penggunaan global yang sah:
- Konfigurasi yang benar-benar perlu bersifat global:
# Pengaturan seluruh aplikasi
debug_mode = False
log_level = "INFO"
def enable_debug():
global debug_mode, log_level
debug_mode = True
log_level = "DEBUG"- Counter untuk debugging atau statistik:
# Melacak pemanggilan fungsi untuk debugging
_function_call_count = 0
def tracked_function():
global _function_call_count
_function_call_count += 1
# ... sisa fungsiPoin Penting tentang global
- Gunakan
globalhanya 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:
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 UnboundLocalErrorPython 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.”
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:
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: 2Setiap 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:
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()globalselalu merujuk ke cakupan level modulnonlocalmerujuk ke cakupan fungsi enclosing terdekat
Kapan Kamu Tidak Bisa Menggunakan nonlocal
Keyword nonlocal hanya bekerja dengan cakupan fungsi enclosing. Kamu tidak bisa memakainya untuk:
- Cakupan global (gunakan
globalsebagai gantinya):
x = "global"
def func():
nonlocal x # SyntaxError: no binding for nonlocal 'x' found
x = "modified"- Variabel yang tidak ada di cakupan enclosing mana pun:
def outer():
def inner():
nonlocal count # SyntaxError: no binding for nonlocal 'count' foundPoin Penting tentang nonlocal
- Gunakan
nonlocaluntuk memodifikasi variabel dari cakupan fungsi enclosing nonlocalmencari di cakupan fungsi enclosing, bukan cakupan global- Membaca variabel enclosing tidak membutuhkan
nonlocal— hanya memodifikasinya yang perlu nonlocalmemungkinkan 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:
x = 42
print(x) # Output: 42
del x
# print(x) # NameError: name 'x' is not definedSetelah 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:
# 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 definedList [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:
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.
# 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'}