2.16. Kapsam

Python bir fonksiyonun içinde bir adı ararken, belirli bir kapsam dizisini tarar. Bu diziyi anlamak, neden bazı atamaların dış adları gölgelediğini, neden bazılarının onları değiştirdiğini ve iç içe fonksiyonların neden tanımlandıkları yerdeki değerleri hatırlayabildiğini açıklar.

Bir modül kapsamının içindeki yerel bir kapsamı, onun da yerleşik kapsamın içinde olduğunu gösteren iç içe kutular; bir ok, ad aramasının en içteki çerçeveden dışarıya doğru ilerlediğini belirtmektedir.

Ad arama yerel fonksiyon kapsamında başlar ve bir eşleşme bulunana kadar dışarıya, modül ve yerleşik kapsamlara doğru ilerler.

2.16.1. Yerel ve modül kapsamı

Bir fonksiyonun içinde tanımlanan adlar o fonksiyona yereldir ve çağrı sona erdiğinde kaybolur:

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

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

Bir .py dosyasının en üst düzeyinde tanımlanan adlar modül düzeyindedir (bazen global olarak adlandırılır) ve fonksiyonların içi de dahil olmak üzere o dosyanın her yerinde görünürdür:

CAMERA = "OpenMV"

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

Bir fonksiyonun içinden modül düzeyindeki bir adı okumak otomatiktir. O ada bir fonksiyonun içinden atama yapmak ise otomatik değildir – atama, çağrının geri kalanı boyunca modül düzeyindekini gölgeleyen yeni bir yerel değişken oluşturur:

counter = 0

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

Soldaki counter, counter‘ı bump içinde yerel bir ad yapar, dolayısıyla sağdaki okumanın bulacağı bir değer olmaz.

2.16.1.1. global anahtar kelimesi

Modül düzeyindeki bir adı bir fonksiyonun içinden gerçekten yeniden atamak için, önce onu global olarak bildirin:

counter = 0

def bump():
    global counter
    counter += 1

global ifadesine az başvurun. Gizli durumu değiştiren fonksiyonların mantığını izlemek, değerleri argüman olarak alıp yeni değerler döndüren fonksiyonlara göre daha zordur. “Durum paylaşmam gerekiyor” sorununun olağan çözümü, bir nesneyi (bir liste, bir dict, bir sınıf örneği) argüman olarak geçirmek ve onu değiştirmektir.

2.16.2. Lambda’lar

Bir lambda, tek bir ifadede küçük bir anonim fonksiyon oluşturur:

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

Bu, tam olarak şuna eşdeğerdir

def square(x):
    return x * x

Bir lambda gövdesi tek bir ifade olmalıdır – ifadeler yok, birden fazla satır yok. Asıl kullanımı, bir fonksiyon alan bir şeye argüman olarak küçücük bir fonksiyon geçirmektir:

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

Gövde bir ifadenin ötesine büyüdüğünde, gerçek bir def kullanmaya geçin. Bir fonksiyonu def ile adlandırmak ona ayrıca traceback’lerde bir ad verir; bir lambda bu ada sahip değildir.

2.16.3. Closure’lar

Başka bir fonksiyonun içinde tanımlanan bir fonksiyon, çevreleyen fonksiyonun kapsamından adları okuyabilir. İç fonksiyon bu adları yakalar ve dış çağrı geri döndükten sonra bile çalışmaya devam eder:

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))

Çıktı:

105 110

add5 ve add10, her biri kendi n değerini hatırlayan iki ayrı fonksiyondur. Bu şekilde özelleştirilmiş bir iç fonksiyon oluşturup döndüren bir fonksiyona closure denir. Bir dilin en başta neden iç içe fonksiyonlara ihtiyaç duyduğunun ana nedeni budur – bir miktar durumu bir fonksiyon değerinin içine yerleştirip ardından o değeri tek bir çağrılabilir olarak elden çıkarmanın bir yolu.

Yakalanan adları okumak otomatik olarak gerçekleşir. Birini yeniden bağlamak ise ek bir anahtar kelime gerektirir. Aşağıdaki örnek doğru görünen şeyi yapar ve başarısız olur:

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

tick içindeki count değişkenine yapılan atama, tıpkı en üst düzey bir fonksiyonda yerel yapacağı gibi, count‘u tick‘e yerel yapar. nonlocal anahtar kelimesi Python’a “bu ad çevreleyen fonksiyonda yaşıyor, onu orada yeniden bağla” der:

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

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

Çıktı:

1 2 3

nonlocal, çevreleyen-fonksiyon kapsamı için neyse, global de modül kapsamı için odur. Yakalanan bir nesneyi değiştirmenin (some_list.append(...), some_dict[k] = v çağırmanın) nonlocal gerektirmediğini unutmayın – ad yeniden bağlanmıyor, yalnızca işaret ettiği nesne değiştiriliyor.