2.16. Ruang lingkup

Saat Python mencari nama di dalam sebuah fungsi, ia menelusuri urutan ruang lingkup yang spesifik. Memahami urutan tersebut menjelaskan mengapa beberapa penugasan menutupi nama luar, mengapa yang lain memodifikasinya, dan mengapa fungsi bersarang dapat mengingat nilai dari tempat mereka didefinisikan.

Nested boxes showing a local scope inside a module scope inside the built-in scope, with an arrow indicating that name lookup walks outward from the innermost frame.

Pencarian nama dimulai dari ruang lingkup fungsi lokal dan berjalan ke luar menuju ruang lingkup modul dan bawaan hingga kecocokan ditemukan.

2.16.1. Ruang lingkup lokal dan modul

Nama yang didefinisikan di dalam sebuah fungsi bersifat lokal untuk fungsi tersebut dan hilang ketika pemanggilan berakhir:

def f():
    x = 10
    print(x)

f()
print(x)              # NameError: x is not defined

Nama yang didefinisikan di tingkat atas file .py bersifat tingkat modul (kadang disebut global) dan terlihat di seluruh file tersebut, termasuk di dalam fungsi:

CAMERA = "OpenMV"

def banner():
    print("running on", CAMERA)

Membaca nama tingkat modul dari dalam fungsi bersifat otomatis. Menetapkan ke nama tersebut dari dalam fungsi tidak -- penugasan membuat variabel lokal baru yang menutupi nama tingkat modul untuk sisa pemanggilan:

counter = 0

def bump():
    counter = counter + 1     # UnboundLocalError

counter di sisi kiri membuat counter menjadi nama lokal di bump, sehingga pembacaan di sisi kanan tidak menemukan nilai.

2.16.1.1. Kata kunci global

Untuk benar-benar menetapkan ulang nama tingkat modul dari dalam fungsi, deklarasikan sebagai global terlebih dahulu:

counter = 0

def bump():
    global counter
    counter += 1

Gunakan global dengan hemat. Fungsi yang mengubah status tersembunyi lebih sulit dipahami dibandingkan fungsi yang mengambil nilai sebagai argumen dan mengembalikan nilai baru. Solusi umum untuk "saya perlu berbagi status" adalah meneruskan objek (list, dict, instance kelas) sebagai argumen dan mengubah objek itu sebagai gantinya.

2.16.2. Lambda

Sebuah lambda membangun fungsi anonim kecil dalam satu ekspresi:

square = lambda x: x * x
square(7)             # 49

Ini sepenuhnya setara dengan

def square(x):
    return x * x

Isi dari sebuah lambda harus berupa satu ekspresi -- tidak ada pernyataan, tidak ada banyak baris. Penggunaan utamanya adalah meneruskan fungsi kecil sebagai argumen ke sesuatu yang menerima fungsi:

pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]

Ketika isi sudah melampaui satu ekspresi, beralih ke def yang sesungguhnya. Menamai fungsi dengan def juga memberikannya nama dalam traceback, yang tidak dimiliki lambda.

2.16.3. Closure

Fungsi yang didefinisikan di dalam fungsi lain dapat membaca nama dari ruang lingkup fungsi yang melingkupinya. Fungsi dalam menangkap nama-nama tersebut dan terus berfungsi bahkan setelah pemanggilan luar selesai:

def make_adder(n):
    def add(x):
        return x + n
    return add

add5 = make_adder(5)
add10 = make_adder(10)
print(add5(100), add10(100))

Output:

105 110

add5 dan add10 adalah dua fungsi terpisah, masing-masing mengingat n-nya sendiri. Fungsi yang membangun dan mengembalikan fungsi dalam yang disesuaikan dengan cara ini disebut closure. Inilah alasan utama mengapa sebuah bahasa membutuhkan fungsi bersarang -- cara untuk menanamkan beberapa status ke dalam nilai fungsi dan kemudian menyerahkan nilai tersebut sebagai satu objek yang dapat dipanggil.

Membaca nama yang ditangkap terjadi secara otomatis. Mengikat ulang nama tersebut membutuhkan kata kunci tambahan. Contoh di bawah ini tampak benar namun gagal:

def make_counter():
    count = 0
    def tick():
        count = count + 1     # UnboundLocalError
        return count
    return tick

Penugasan ke count di dalam tick membuat count lokal terhadap tick, sama seperti halnya membuat nama lokal dalam fungsi tingkat atas. Kata kunci nonlocal memberi tahu Python "nama ini berada di fungsi yang melingkupi, ikat ulang di sana":

def make_counter():
    count = 0
    def tick():
        nonlocal count
        count += 1
        return count
    return tick

c = make_counter()
print(c(), c(), c())

Output:

1 2 3

nonlocal terhadap ruang lingkup fungsi yang melingkupi setara dengan global terhadap ruang lingkup modul. Perlu diperhatikan bahwa mengubah objek yang ditangkap (memanggil some_list.append(...), some_dict[k] = v) tidak membutuhkan nonlocal -- nama tidak diikat ulang, hanya objek yang ditunjuknya yang diubah.