İçeriğe geç
academia.sh

Ders 10 / 16

Kapsam Kuralları

Ad araması dört düzeyde yürür ve hangi düzeyde durduğunu adın okunduğu yer değil, atandığı yer belirler: gövdeye tek bir atama satırı eklemek aynı okumayı geçerli bir değerden UnboundLocalError'a çevirir.

İçindekiler

Önceki ders argümanların parametrelere nasıl bağlandığını ölçtü. Bağlanan her argüman gövdede bir ad kurar; ama gövde yalnız o adları kullanmaz. Kendi kurduğu adlar, çevreleyen tanımdan gelenler, dosyanın en dışında duranlar ve dilin kendi sağladıkları da oradadır — ve hepsi aynı yazımla, çıplak bir adla okunur.

Programlama Temelleri kursunun kapsam dersi bu arama sırasını dilden bağımsız bir kavram olarak kurdu: yerelden başlanır, çevreleyene ve küresele gidilir, yerleşiklerle biter, ilk bulunan kazanır. O ders kapsamı görünürlükle, yaşam süresini ömürle tanımladı ve gölgelemenin ne olduğunu anlattı. Burada aynı sıra bir arama zinciri olarak ölçülür ve ölçüm bir soruya odaklanır: bir adın hangi düzeyde aranacağını belirleyen şey nedir? Yanıt sezgiye aykırıdır — belirleyen şey adın okunduğu yer değil, gövdede bir yerde atanıp atanmadığıdır.

Dört Düzey

Python’da ad araması dört düzeyde yürür ve sırası sabittir:

  1. Yerel — içinde bulunulan fonksiyon gövdesi.
  2. Kapsayan — onu çevreleyen fonksiyon gövdeleri, içten dışa.
  3. Genel — dosyanın en dış düzeyi.
  4. Yerleşik — dilin kendi sağladığı adlar.

Arama ilk bulduğu yerde durur ve alttaki düzeylere hiç bakmaz. Dördü de bulamazsa sonuç bir değer değil, NameError olur.

Dördüncü düzey öbürlerinden farklıdır: içindeki adları program yazmaz, dil koyar. Ama arama sırasındaki yeri özel değildir — en sonda olduğu için, üstteki üç düzeyden herhangi biri aynı adı tanımlarsa yerleşik olan gölgelenir ve o gövdede erişilemez olur.

Adı Yerel Yapan Şey

Asıl kural buradadır ve gövdenin tamamını ilgilendirir. Python bir gövdedeki adın hangi düzeye ait olduğuna çalışma sırasında değil, gövdeyi okurken karar verir. Kural tek cümledir: gövdede bir yerde ada atama yapılıyorsa, o ad o gövdenin tamamı için yereldir.

“Bir yerde” ifadesi harfiyen alınmalıdır. Atama gövdenin en son satırında olsa bile ad gövdenin en başından itibaren yereldir. Bu yüzden atamadan önce yapılan bir okuma, dıştaki değeri bulmaz — henüz değer almamış yerel adı bulur ve sonuç bir istisna olur: UnboundLocalError.

Bu, gölgelemenin ölçülebilir hâlidir. Programlama Temelleri kursu gölgelemeyi “dıştaki ad erişilemez hâle gelir” diye tanımlamıştı; buradaki ölçüm erişilemezliğin nereden başladığını söylüyor — atama satırından değil, gövdenin ilk satırından.

Dışarı Yazmak

Kuralın doğrudan sonucu şudur: dıştaki bir ada atama yapmak çıplak yazımla olanaksızdır. Atama her zaman yerel bir ad kurar; dıştakini değiştirmez, onu gölgeler.

Dil bu yüzden iki bildirim tanır. global, adın genel düzeye ait olduğunu bildirir ve o gövdedeki atamaları oraya yönlendirir. nonlocal, adın kapsayan bir gövdeye ait olduğunu bildirir ve atamaları oraya yönlendirir; genel düzeye bakmaz, bulamazsa hata verir.

Okumak için bildirim gerekmez. Bildirim yalnız yazma yönünü değiştirir; kural yazma kuralıdır, okuma kuralı değil. Bu asimetri kasıtlıdır: dıştaki bir değeri okumak o değeri kimse için değiştirmez, ama ona yazmak gövdenin dışında görülebilir bir sonuç bırakır. Bildirim zorunluluğu, o sonucun kazara doğmasını engeller — dışarı yazan her gövde bunu tek satırla ilan etmek zorundadır.

Ölçümün varsayımları:

  • PA24 — Kâhin, her düzeyin o düzeyi adlandıran ayrı bir dizgi bağlamasıdır; dönen değer, aramanın hangi düzeyde durduğunu doğrudan söyler.
  • PA25 — Aynı ad SINIR dört durumda ölçülür ve durumlar yalnız hangi düzeylerin o adı tanımladığı bakımından ayrılır; gövdelerin okuma satırı birebir aynıdır.
  • PA26 — Yerleşik düzey ayrı bir adla ölçülür, çünkü o düzeydeki adları program yazamaz. Ölçüm için dilin sağladığı min adı kullanılır ve üstteki düzeylerde gölgelenir.
  • PA27 — Atama kuralı üç gövdeyle ölçülür: yalnız okuyan, okuyup sonra atayan, yalnız atayan. Üçünde de okuma satırı aynıdır; değişen tek şey gövdede bir atamanın bulunup bulunmadığı ve yeridir.
  • PA28 — Yazma yönü üç durumda ölçülür: bildirimsiz, nonlocal bildirimiyle ve global bildirimiyle. Genel düzeydeki ad ölçümün sonunda değiştiği için o satır en sonda çalıştırılır.
  • PA29 — İstisnalar yalnız sınıf adıyla yazılır, iletileri yazılmaz.
  • PA30 — Kapanış ölçümünde üç işlev bir döngüde üretilir ve döngü bittikten sonra çağrılır. Çağrının döngüden sonraya bırakılması kasıtlıdır; ölçülen şey adın ne zaman çözüldüğüdür, hangi değeri taşıdığı değil.

Ölçüm

"""Dort kapsam duzeyi: ad hangi duzeyde bulunur, atama onu nereye tasir."""

SINIR = "genel"


def olc(islev):
    try:
        return islev()
    except Exception as e:
        return f"!{type(e).__name__}"


def hicbiri():
    return TANIMSIZ_AD


def yalniz_genel():
    return SINIR


def kapsayan_da_var():
    SINIR = "kapsayan"

    def ic():
        return SINIR
    return ic()


def yerel_de_var():
    SINIR = "kapsayan"

    def ic():
        SINIR = "yerel"
        return SINIR
    return ic()


ARAMA = (
    ("hiçbir düzeyde yok", hicbiri, "-"),
    ("yalnız genel", yalniz_genel, "genel"),
    ("genel + kapsayan", kapsayan_da_var, "genel, kapsayan"),
    ("genel + kapsayan + yerel", yerel_de_var, "genel, kapsayan, yerel"),
)

print(f"{'adın tanımlı olduğu düzeyler':<26s} {'bulunan':<22s} sonuç")
for ad, f, tanimli in ARAMA:
    print(f"  {ad:<24s} {tanimli:<22s} {olc(f)}")


def yerlesigi_kullanir():
    return min([3, 1, 2])


def yerlesigi_kapsayan_golgeler():
    min = "kapsayan gölgeledi"

    def ic():
        return min
    return ic()


def yerlesigi_yerel_golgeler():
    def ic():
        min = "yerel gölgeledi"
        return min
    return ic()


print()
print(f"{'yerleşik ad min':<30s} sonuç")
for ad, f in (("hiçbir düzey tanımlamaz", yerlesigi_kullanir),
              ("kapsayan düzey tanımlar", yerlesigi_kapsayan_golgeler),
              ("yerel düzey tanımlar", yerlesigi_yerel_golgeler)):
    print(f"  {ad:<28s} {olc(f)}")


def yalniz_okur():
    return SINIR


def okur_sonra_atar():
    once = SINIR
    SINIR = "yerel"
    return once


def yalniz_atar():
    SINIR = "yerel"
    return SINIR


print()
print(f"{'gövdede SINIR ile yapılan':<30s} {'gövdedeki okuma':<18s} sonuç")
for ad, f, okuma in (("yalnız okunur", yalniz_okur, "atamadan önce yok"),
                     ("okunur, sonra atanır", okur_sonra_atar, "atamadan önce"),
                     ("yalnız atanır", yalniz_atar, "atamadan sonra")):
    print(f"  {ad:<28s} {okuma:<18s} {olc(f)}")


def genel_bildirir():
    global SINIR
    SINIR = "içeriden yazıldı"


def kapsayan_bildirir():
    SINIR = "kapsayan"

    def ic():
        nonlocal SINIR
        SINIR = "içeriden yazıldı"
    ic()
    return SINIR


def bildirimsiz_yazar():
    SINIR = "kapsayan"

    def ic():
        SINIR = "içeriden yazıldı"
    ic()
    return SINIR


print()
print(f"{'gövdeden dışarı yazma':<26s} {'hedef düzey':<12s} dıştaki adın sonraki değeri")
for ad, f, hedef in (("bildirim yok", bildirimsiz_yazar, "kapsayan"),
                     ("nonlocal bildirimi", kapsayan_bildirir, "kapsayan")):
    print(f"  {ad:<24s} {hedef:<12s} {olc(f)}")

once = SINIR
genel_bildirir()
print(f"  {'global bildirimi':<24s} {'genel':<12s} {once} -> {SINIR}")


def gec_baglanan():
    islevler = []
    for i in range(3):
        def uret():
            return i
        islevler.append(uret)
    return [f() for f in islevler]


def varsayilanla_yakalanan():
    islevler = []
    for i in range(3):
        def uret(i=i):
            return i
        islevler.append(uret)
    return [f() for f in islevler]


def dongu_sonrasi_ad():
    for i in range(3):
        pass
    return i


print()
print(f"{'döngüde üretilen üç işlev':<30s} çağrıldıklarında dönen")
print(f"  {'gövdede çıplak ad':<28s} {gec_baglanan()}")
print(f"  {'varsayılanla yakalanmış ad':<28s} {varsayilanla_yakalanan()}")
print(f"  {'döngü bittikten sonra ad':<28s} {dongu_sonrasi_ad()}")
adın tanımlı olduğu düzeyler bulunan                sonuç
  hiçbir düzeyde yok       -                      !NameError
  yalnız genel             genel                  genel
  genel + kapsayan         genel, kapsayan        kapsayan
  genel + kapsayan + yerel genel, kapsayan, yerel yerel

yerleşik ad min                sonuç
  hiçbir düzey tanımlamaz      1
  kapsayan düzey tanımlar      kapsayan gölgeledi
  yerel düzey tanımlar         yerel gölgeledi

gövdede SINIR ile yapılan      gövdedeki okuma    sonuç
  yalnız okunur                atamadan önce yok  genel
  okunur, sonra atanır         atamadan önce      !UnboundLocalError
  yalnız atanır                atamadan sonra     yerel

gövdeden dışarı yazma      hedef düzey  dıştaki adın sonraki değeri
  bildirim yok             kapsayan     kapsayan
  nonlocal bildirimi       kapsayan     içeriden yazıldı
  global bildirimi         genel        genel -> içeriden yazıldı

döngüde üretilen üç işlev      çağrıldıklarında dönen
  gövdede çıplak ad            [2, 2, 2]
  varsayılanla yakalanmış ad   [0, 1, 2]
  döngü bittikten sonra ad     2

Zincirin Dört Basamağı

Üst tablo arama zincirini basamak basamak kuruyor. Dört satırın okuma satırı birebir aynı; değişen tek şey hangi düzeylerin o adı tanımladığı.

Hiçbir düzeyde tanımlı olmadığında sonuç NameError. İkinci satırda ad yalnız genel düzeyde var ve bulunan o. Üçüncü satırda kapsayan düzey de aynı adı tanımlıyor ve kapsayan kazanıyor — genel düzeydeki ad hâlâ orada duruyor ama aramaya hiç gelmiyor. Dördüncüde yerel düzey de ekleniyor ve bu kez yerel kazanıyor.

Örüntü şudur: her yeni düzey, kendinden dıştaki her şeyi kapatıyor. Arama dıştan içe değil, içten dışa yürüyor ve ilk bulduğunda duruyor. Üç düzey aynı adı tanımladığında üçü yarışmıyor; en içteki öbür ikisini sınamaya bile sokmuyor.

Orta tablo dördüncü düzeyi ekliyor. İlk satırda hiçbir düzey min adını tanımlamıyor ve arama yerleşik düzeye kadar inip dilin sağladığı adı buluyor; sonuç, üç öğelik listenin en küçüğü olan 1. Sonraki iki satırda aynı ad önce kapsayan, sonra yerel düzeyde tanımlanıyor ve ikisinde de yerleşik olan gölgeleniyor. Yerleşik düzeyin arama sırasında ayrıcalığı yok; en sonda olduğu için her zaman en kolay gölgelenen düzey.

Bunun pratik sonucu, bir yerleşiğin adını yerel bir değişkene vermenin o gövdede o yerleşiği kullanılamaz kılmasıdır. Hata çıkmaz, uyarı çıkmaz; ad artık başka bir şeyi gösterir.

Tek Bir Atama Satırı

Üçüncü tablo dersin ana ölçümüdür ve üç satırın okuma satırı yine aynıdır.

Birinci satır: gövde SINIR adını yalnız okuyor, hiçbir yerde atamıyor. Arama zinciri yürüyor, genel düzeyde bulunuyor, sonuç genel.

İkinci satır: gövde önce aynı okumayı yapıyor, sonra ada bir atama yapıyor. Okuma satırı atamadan öncedir ve birinci satırdakiyle karakter karakter aynıdır. Ama sonuç artık bir değer değil, UnboundLocalError.

Aradaki tek fark, gövdenin ilerideki bir satırında bir atamanın bulunmasıdır. O atama adı gövdenin tamamı için yerel yapıyor; okuma satırına gelindiğinde arama artık genel düzeye hiç çıkmıyor, yerel düzeyde duruyor ve orada henüz değer almamış bir ad buluyor. Zincir kırılmıyor — hiç kurulmuyor.

Buradaki sıra ilişkisi kritiktir: adın yerel olmasına karar veren şey çalışma sırası değil, gövdenin tümüne bakılarak verilen bir karardır. Bu yüzden hatayı üreten satır, hatanın nedenini içermez; neden aşağıdaki atama satırıdır. Üçüncü satır bunu tamamlıyor — gövde yalnız atayıp sonra okuduğunda sorun yok, sonuç yerel.

Yazmanın Üç Yolu

Alt tablo yazma yönünü ölçüyor. Birinci satırda iç gövde bildirimsiz bir atama yapıyor ve dıştaki ad değişmiyor: sonuç hâlâ kapsayan. Atama dıştakini güncellemedi, iç gövdeye ait yeni bir ad kurdu ve o ad gövde bitince ortadan kalktı. Yazılan satır bir güncelleme gibi görünüyor; yaptığı iş bir gölgeleme.

İkinci satırda aynı gövde nonlocal bildirimiyle yazıyor ve dıştaki ad değişiyor. Üçüncü satırda global bildirimi genel düzeydeki adı değiştiriyor; ölçümden önce genel olan değer sonrasında içeriden yazıldı oluyor.

Üç satırın söylediği şudur: gövdeden dışarı yazmak bildirimle olur, çıplak atamayla olmaz. Bildirim yeni bir yetenek eklemiyor; yalnız atamanın hangi düzeyi hedeflediğini söylüyor. Bildirim olmadığında hedef her zaman yereldir ve bu, önceki tablodaki UnboundLocalError ile aynı kuralın öbür yüzüdür — biri okumada, öbürü yazmada görünür.

Bu ayrım, dıştaki bir nesneyi değiştirmekle dıştaki adı yeniden bağlamak arasındaki farkı da açıklar. Önceki dersteki paylaşılan kova hiçbir bildirim olmadan büyüyordu, çünkü orada yapılan şey ada atama değil, adın gösterdiği nesneyi değiştirmekti. Kapsam kuralları adların bağlanmasını yönetir; nesnelerin içeriğini değil.

Adın Ne Zaman Çözüldüğü

Son tablo zincirin ne zaman yürüdüğünü ölçüyor ve buraya kadarki her şeyi tek bir sonuçta topluyor. Bir döngüde üç işlev üretiliyor; her biri gövdesinde döngü değişkenini çıplak bir adla okuyor. Döngü bittikten sonra üçü de çağrılıyor.

Beklenen sonuç [0, 1, 2], ölçülen sonuç [2, 2, 2]. Üç işlev de aynı değeri döndürüyor.

Nedeni kapsam kuralının kendisidir. İşlevlerin gövdesindeki ad, işlev üretildiğinde çözülmüyor; çağrıldığında çözülüyor. Üretim sırasında gövdeye yazılan şey bir değer değil, bir addır — ve o ad kapsayan gövdedeki tek bir değişkeni gösterir. Üç işlev üç ayrı değeri değil, aynı adı taşıyor. Çağrıldıklarında arama zinciri yürüyor, kapsayan düzeydeki adı buluyor ve orada döngünün bıraktığı son değer duruyor.

Üçüncü satır o son değerin ne olduğunu gösteriyor: döngü bittikten sonra da ad yaşıyor ve değeri 2. Python’da döngü kendi kapsamını açmaz; döngü değişkeni onu çevreleyen gövdeye aittir ve döngü bitince ortadan kalkmaz. Programlama Temelleri kursu döngü değişkeninin yerel olduğunu söylemişti ve bu doğrudur — ölçüm onu daraltıyor: yerel olduğu birim döngü değil, fonksiyon gövdesinin tamamıdır. Süslü parantezle ya da anahtar sözcükle kapatılan blokların kendi kapsamını açtığı dillerde bu satır bir hata verirdi; burada bir değer veriyor.

İkinci satır çözümü veriyor ve çözüm önceki dersin kuralıdır. Ad varsayılan değer olarak yazıldığında sonuç [0, 1, 2] oluyor. Varsayılan ifadesi tanım okunurken bir kez değerlendirilir; tanım burada döngü gövdesinde olduğu için her turda yeniden çalışıyor ve o turdaki değeri yakalıyor. Değer, arama zincirine bırakılmak yerine tanım anında kopyalanmış oluyor.

İki dersin kuralı burada birleşiyor: kapsam adın nerede aranacağını söylüyor, varsayılanın değerlendirilme zamanı ise ne zaman aranacağını değiştiriyor. Aynı üç satırlık döngü, tek bir imza değişikliğiyle iki ayrı sonuç veriyor.

Özet

  • Ad araması dört düzeyde ve sabit bir sırada yürür — yerel, kapsayan, genel, yerleşik — ilk bulduğunda durur ve dördü de bulamazsa NameError verir.
  • Her düzey kendinden dıştakileri kapatır: aynı ad üç düzeyde tanımlıysa en içteki kazanır ve öbürleri aramaya hiç girmez.
  • Yerleşik düzeyin arama sırasında ayrıcalığı yoktur; en sonda olduğu için üstteki herhangi bir düzeyde aynı adın tanımlanması yerleşiği o gövdede erişilemez kılar.
  • Bir adın yerel olmasına karar veren şey okumanın yeri değil, gövdede bir yerde ona atama yapılmasıdır; atama en sonda olsa bile ad gövdenin başından itibaren yereldir ve atamadan önceki okuma UnboundLocalError üretir.
  • Gövdeden dışarı yazmak bildirimle olur: nonlocal atamayı kapsayan düzeye, global genel düzeye yönlendirir. Bildirimsiz atama dıştakini güncellemez, onu gölgeleyen yeni bir yerel ad kurar.
  • Gövdedeki bir ad, işlev üretildiğinde değil çağrıldığında çözülür: döngüde üretilen üç işlev aynı adı paylaştığı için üçü de [2, 2, 2] döndürür; adı varsayılan değer olarak yakalamak sonucu [0, 1, 2] yapar.

Sonraki Adım

Arama zincirinin son basamağındaki adlar buraya kadar yalnız gölgelenecek bir şey olarak göründü. Oysa o düzeyde duran adlar dilin en çok kullanılan işlevleridir ve her biri, ilk iki dersin ölçtüğü protokollere doğrudan bağlanır: uzunluk soran, metne çeviren, yineleyici isteyen, doğruluk sınayan, sıralayan. Sonraki ders bu yerleşikleri sözleşmeleriyle ölçer — her birinin hangi özel yöntemi çağırdığını ve o yöntem tanımlı değilse ne olduğunu. Ortaya çıkan şey, yerleşiklerin iş yapan işlevler değil, protokol çağıran ince kabuklar olduğudur.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat