Belajar dengan membaca secara berurutan

Inner Function dan Closure — Menguasai Scope dengan global dan nonlocal

Pelajari scope, inner function, dan closure di Python untuk menguasai aliran variabel di dalam fungsi.

Pada artikel sebelumnya kamu sudah melihat bagaimana argumen dan nilai kembalian berperilaku. Kali ini kamu akan menggali beberapa topik lain seputar fungsi: bagaimana Python memisahkan variabel di dalam dan di luar fungsi (scope), cara mendefinisikan fungsi di dalam fungsi (inner function), dan cara mengembalikan fungsi yang mengingat nilai luar (sebuah closure). Sambil jalan, kamu akan tahu kapan menggunakan global dan nonlocal untuk menulis ulang variabel di scope luar dari dalam fungsi.

Variabel Global dan Local — Scope Itu Terpisah

Variabel yang didefinisikan di luar fungsi adalah variabel global; variabel yang didefinisikan di dalam fungsi adalah variabel local. Kamu bisa membaca variabel global dari dalam fungsi, tetapi jika kamu menetapkan nilai ke nama yang sama dengan = value di dalam fungsi, Python membuat variabel local baru — sesuatu yang terpisah dari yang di luar.

Jika kamu memeriksa alamat memori variabel dengan id(), kamu akan melihat bahwa yang di luar dan yang di dalam menunjuk ke lokasi yang berbeda.

stock = 100   # variabel global

def show_stock():
    print(f"di dalam: {stock}")   # 100 — membaca nilai luar

def try_change():
    stock = 50                       # membuat variabel local yang baru
    print(f"di dalam: {stock}")   # 50

show_stock()                         # di dalam: 100
try_change()                         # di dalam: 50
print(f"di luar: {stock}")          # di luar: 100  ← yang di luar tidak berubah
Di Dalam vs. Di Luar Adalah Scope Terpisah — Pandangan Diagram Himpunan
Module (Global Namespace)
  • stock = 100 — variabel global
  • Fungsi hanya bisa membaca dari dalam
Local Namespace show_stock()
  • print(stock) → membaca nilai luar 100
  • Tidak membuat variabel local
Local Namespace try_change()
  • stock = 50 — membuat local baru di dalam fungsi
  • Tidak memengaruhi stock di luar
Local namespace bersarang di dalam module. Pembacaan menjangkau ke luar, tapi penetapan nilai tetap tertutup sebagai local terpisah.

Gunakan variabel global stock untuk memastikan bahwa pembacaan menjangkau ke luar, tapi penetapan di dalam tidak.

① Deklarasikan global stock = 100.

② Definisikan def show_stock(): dan cetak f"di dalam: {stock}".

③ Definisikan def try_change(): lalu tetapkan stock = 50, kemudian cetak f"di dalam: {stock}".

④ Panggil show_stock()try_change()print(f"di luar: {stock}") dan periksa bahwa nilai di luar tidak berubah.

(Saat jawaban benar, penjelasan akan muncul.)

Python Editor

Jalankan kode untuk melihat output

Menulis Ulang Variabel Luar dengan Keyword global

Saat kamu memang ingin menulis ulang variabel global luar dari dalam fungsi, deklarasikan global nama_variabel di awal fungsi. Itu memberi tahu Python, "ini adalah global yang di luar, bukan local baru." Tanpa itu, penetapan seperti count += 1 mencampur pembacaan dan penulisan pada nama yang sama dan kamu akan mendapatkan UnboundLocalError ("local variable referenced before assignment").

global Mendeklarasikan "Variabel Luar Itu"
tanpa globalcount += 1UnboundLocalErrorglobal countcount += 1menulis ulangcount luargagalberhasil
visit_count = 0

# × Tanpa global → UnboundLocalError
# def increment():
#     visit_count += 1
# increment()   ← UnboundLocalError

# ○ global memungkinkan kamu menulis ulang global luar
def increment():
    global visit_count
    visit_count += 1

increment()
increment()
increment()
print(visit_count)   # 3

Pakai global Seminimal Mungkin

global membuat fungsi diam-diam menulis ulang state luar, sehingga satu penulisan buruk yang berjarak 100 baris menjadi sangat sulit dilacak pada skala besar.

Utamakan mengembalikan nilai dengan return dan menetapkannya di sisi pemanggil, dan gunakan class (akan dibahas nanti) ketika kamu memang butuh perilaku berstate.

Bangun counter untuk kunjungan halaman menggunakan global.

① Deklarasikan visit_count = 0 di luar fungsi mana pun.

② Definisikan def increment_visit():. Di awal fungsi, tulis global visit_count, lalu visit_count += 1.

③ Panggil increment_visit() tiga kali, lalu cetak f"kunjungan: {visit_count}".

Python Editor

Jalankan kode untuk melihat output

Bagaimana Fungsi dan Variabel Hidup di Memori — Nama vs. Objek

Secara internal, Python menyimpan namespace — pencarian "nama → objek". x = 5 berarti "buat integer 5 di memori dan buat nama x menunjuk ke sana."

def f(): ... bekerja dengan cara yang sama: ia membangun sebuah objek fungsi (badan fungsi) di memori dan membuat nama f menunjuk ke sana. Hanya ada satu pencarian seperti ini per program — global namespace — disiapkan saat module dimuat dan tetap ada sampai program keluar.

Frame Module dan Fungsi di Memori
Global Namespace (Module)
  • x = 5 — nama x menunjuk ke integer 5
  • def f — nama f menunjuk ke objek fungsi
  • Tetap hidup sampai program keluar
Frame Panggilan Pertama f()
  • Menyimpan argumen dan local
  • Dibuang seluruhnya saat return
Frame Panggilan Kedua f()
  • Sepenuhnya terpisah dari yang pertama
  • Local-nya independen
Setiap panggilan fungsi mendapat local namespace (frame) baru di memori, lalu hilang saat keluar. Hanya ada satu global namespace untuk seluruh program.
Bagaimana def dan Pemanggilan Fungsi Memakai Memori
①Jalankan`def f(): ...`Daftarkan fungsidi global nsTetap di global nssampai program keluar②Panggilan pertama: f()Buat local namespacebaru (frame)Buang framesaat `return`③Panggilan kedua: f()Bangun frame baru(independen dari #1)Buang framesaat `return`saat run timepanggilsaat keluarpanggil lagisaat keluar
Jalankan def sekali dan fungsi terdaftar di global namespace, di mana ia tetap ada sampai keluar. Namun setiap panggilan membangun frame local-nya sendiri dan membuangnya saat return. Inner function, closure, dan nonlocal yang dibahas berikutnya semua dibangun di atas struktur frame ini.

Inner Function — Mendefinisikan Fungsi di Dalam Fungsi

Tumpuk satu lagi def di dalam fungsi dan kamu mendapat inner function — fungsi yang hanya ada untuk dipakai di dalam fungsi luar. Itu cara yang bagus untuk menamai potongan logika yang bermakna di dalam fungsi panjang sehingga badan fungsi terbaca sebagai urutan langkah yang rapi.

Karena inner function tidak terjangkau dari luar, ia juga cocok untuk menyembunyikan helper yang tidak ingin kamu ekspos.

Scope Inner Function — Pandangan Diagram Himpunan
Module (Global Namespace)
  • Memanggil validate dari sini → NameError
  • Tidak ada di luar
Frame process_user()
  • Menyimpan argumen name / age
  • Bisa memanggil validate dari dalam
Frame validate()
  • Membaca name / age luar
  • Tidak terekspos ke luar
validate hanya hidup di dalam process_user. Ia bisa membaca argumen luar, dan dunia luar tidak pernah melihatnya.
def process_user(name, age):
    def validate():
        if not name or not isinstance(age, int) or age < 0:
            raise ValueError("Input tidak valid")

    validate()                       # panggil inner function
    print(f"Diproses: {name} ({age})")

process_user("Budi", 25)
# Diproses: Budi (25)

# validate tidak terjangkau dari luar process_user
# validate()   ← NameError

Bagus Saat Kamu Ingin Memecah Fungsi Panjang

Begitu fungsi melewati 50 atau 100 baris, memecahnya menjadi inner function yang dinamai untuk tiap potongan bermaknaprocess_name() / process_age() dan seterusnya — mengubah fungsi luar menjadi daftar langkah yang mudah dibaca. Jika nantinya kamu ingin memakai ulang salah satunya di luar, mempromosikan inner function menjadi fungsi biasa itu sepele.

Susun ulang fungsi pemrosesan pesanan dengan inner function khusus untuk pengecekan input.

① Definisikan def process_order(item, quantity):.

② Di dalamnya, definisikan def validate():. Di dalam itu, raise ValueError("Pesanan tidak valid") jika item kosong, quantity bukan int, atau quantity adalah 0 atau di bawahnya.

③ Panggil validate(), lalu cetak f"Pesanan diterima: {quantity} x {item}".

④ Panggil process_order("apel", 3) dan periksa hasilnya.

Python Editor

Jalankan kode untuk melihat output

Closure — Fungsi yang Mengingat Nilai Luar

Inner function bisa membaca argumen dan local fungsi luar. Ambil satu langkah lebih jauh — minta fungsi luar return inner function — dan kamu sudah membangun fungsi yang terus bekerja dengan nilai luar yang sudah tertanam. Itulah closure.

Ini cara rapi untuk memproduksi massal fungsi serupa yang hanya berbeda pada satu pengaturan, seperti "fungsi yang melipattigakan" dan "fungsi yang melipatlimakan". Ambil pengaturan itu (factor) sebagai argumen luar dan referensikan dari inner function: make_multiplier(3) mengembalikan "sebuah multiply yang mengingat 3", dan make_multiplier(5) mengembalikan "sebuah multiply yang mengingat 5".

def make_multiplier(factor):
    def multiply(x):
        return x * factor    # mereferensikan factor luar
    return multiply

times3 = make_multiplier(3)   # fungsi yang mengingat factor=3
times5 = make_multiplier(5)   # fungsi terpisah yang mengingat factor=5

print(times3(10))   # 30
print(times5(10))   # 50
print(times3(7))    # 21
Cara Kerja Closure — Definisi dan Alur Pemanggilan
①Panggilmake_multiplier(3)②Frame luardibangun dengan factor=3④return multiply(mengingat factor=3)③def multiplymenunjuk factor di dalam⑤times3 = make_multiplier(3)⑥times3(10)→ 10 × 3(factor) = 30jalankandefinisikan di dalamkembalikan fungsi pengingatterimapanggil
Frame luar make_multiplier hilang saat return, tapi multiply yang dibangun di dalamnya keluar dengan ingatan factor=3.

Closure Memberi "Fungsi Terkonfigurasi-di-Awal"

Saat kamu ingin membuat banyak perhitungan serupa yang hanya berbeda pada satu pengaturan — "pajak 10%" vs "pajak 8%" dan seterusnya — closure bersinar. Alih-alih meneruskan pengaturan di setiap panggilan, kamu memberikan satu fungsi yang sudah terkonfigurasi, dan kode pemanggil tetap jauh lebih bersih.

Bangun fungsi yang mengembalikan fungsi penerap diskon yang mengingat tarif diskon.

① Definisikan def make_discounter(rate):. Di dalamnya, definisikan def apply(price): yang mengembalikan int(price * (1 - rate)). Jangan lupa return apply.

② Bangun dua fungsi: discount_10 = make_discounter(0.1) dan discount_30 = make_discounter(0.3).

③ Cetak discount_10(1000) dan discount_30(1000), dan periksa bahwa tarif diskon yang berbeda diterapkan ke harga yang sama.

Python Editor

Jalankan kode untuk melihat output

nonlocal — Menulis Ulang Variabel Fungsi yang Membungkus

Closure bisa membaca variabel luar dengan baik, tapi seperti pada global, mencoba menulis ulang-nya melalui count += 1 akan meledak dengan UnboundLocalError. Keyword yang mengizinkan penulisan ulang itu adalah nonlocal. Jika global menargetkan level module, nonlocal menargetkan local fungsi yang langsung membungkus.

Bungkus State di Dalam Objek Fungsi

nonlocal adalah cara kanonis untuk membungkus counter yang bertambah pada setiap panggilan di dalam sebuah fungsi.

Tidak seperti global, state tetap tertutup di dalam objek fungsi tertentu, sehingga efek samping tidak menyebar, dan kamu mendapatkan state yang lebih aman daripada menggunakan global.

def create_counter():
    x = 0
    def increment():
        nonlocal x      # mendeklarasikan kita memperbarui x dari create_counter
        x += 1
        return x
    return increment

counter = create_counter()
print(counter())   # 1
print(counter())   # 2
print(counter())   # 3

# Counter kedua memiliki x sendiri yang independen
counter2 = create_counter()
print(counter2())  # 1 — tidak berhubungan dengan counter
nonlocal Memperbarui Variabel Fungsi yang Membungkus — Pandangan Diagram Himpunan
Module (Global Namespace)
  • counter = create_counter() — menerima increment
  • Kode di luar tidak bisa menyentuh x secara langsung
Frame create_counter()
  • x = 0 — counter, dibuat tepat sekali
  • Yang dirujuk increment melalui nonlocal
Frame increment()
  • nonlocal x — menunjuk ke x luar
  • x += 1 memperbarui x luar
nonlocal menargetkan variabel fungsi yang langsung membungkus. State hidup di dalam objek fungsi, bukan di global namespace.

Bangun sesuatu seperti fungsi ID generator yang mengeluarkan ID pesanan mulai dari 1, menggunakan closure dengan nonlocal.

① Definisikan def create_order_id_issuer(): dan inisialisasi next_id = 1 di awal.

② Di dalamnya, definisikan def issue():. Setelah nonlocal next_id, tulis current = next_idnext_id += 1return current.

③ Terakhir return issue untuk menyerahkan inner function.

④ Dapatkan via issue_id = create_order_id_issuer() dan panggil print(issue_id()) tiga kali — pastikan kamu melihat 1 → 2 → 3.

Python Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Apa yang dicetak kode ini?
stock = 100
def f():
stock = 50
f()
print(stock)

Soal 2Mengapa kode ini error? Pilih penjelasan terbaik.
count = 0
def inc():
count += 1
inc()

Soal 3Apa yang dicetak print(times3(10))?
def make_multiplier(factor):
def multiply(x):
return x * factor
return multiply
times3 = make_multiplier(3)